mirror of https://github.com/minetest/minetest.git
Merge branch 'master' into Eyecandy-Effects
This commit is contained in:
commit
65d9fcdd49
|
@ -1,2 +1,5 @@
|
|||
# Forces all files which git considers text files to use LF line endings
|
||||
* text=auto eol=lf
|
||||
|
||||
*.cpp diff=cpp
|
||||
*.h diff=cpp
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
index fd5a056e3..83e3cf657 100644
|
||||
--- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
+++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java
|
||||
@@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
}
|
||||
|
||||
- if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
|
||||
+ if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE ||
|
||||
+ /*
|
||||
+ * CUSTOM ADDITION FOR MINETEST
|
||||
+ * should be upstreamed
|
||||
+ */
|
||||
+ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) {
|
||||
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
|
||||
// they are ignored here because sending them as mouse input to SDL is messy
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
|
|
@ -20,7 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
package net.minetest.minetest;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
@ -32,6 +33,7 @@ import android.view.WindowManager;
|
|||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
@ -45,12 +47,29 @@ import java.util.Objects;
|
|||
// This annotation prevents the minifier/Proguard from mangling them.
|
||||
@Keep
|
||||
@SuppressWarnings("unused")
|
||||
public class GameActivity extends NativeActivity {
|
||||
static {
|
||||
System.loadLibrary("c++_shared");
|
||||
System.loadLibrary("minetest");
|
||||
public class GameActivity extends SDLActivity {
|
||||
@Override
|
||||
protected String getMainSharedObject() {
|
||||
return getContext().getApplicationInfo().nativeLibraryDir + "/libminetest.so";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMainFunction() {
|
||||
return "SDL_Main";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {
|
||||
"minetest"
|
||||
};
|
||||
}
|
||||
|
||||
// Prevent SDL from changing orientation settings since we already set the
|
||||
// correct orientation in our AndroidManifest.xml
|
||||
@Override
|
||||
public void setOrientationBis(int w, int h, boolean resizable, String hint) {}
|
||||
|
||||
enum DialogType { TEXT_INPUT, SELECTION_INPUT }
|
||||
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
|
||||
|
||||
|
@ -59,32 +78,6 @@ public class GameActivity extends NativeActivity {
|
|||
private String messageReturnValue = "";
|
||||
private int selectionReturnValue = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
private void makeFullScreen() {
|
||||
this.getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus)
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
makeFullScreen();
|
||||
}
|
||||
|
||||
private native void saveSettings();
|
||||
|
||||
@Override
|
||||
|
@ -96,11 +89,6 @@ public class GameActivity extends NativeActivity {
|
|||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Ignore the back press so Minetest can handle it
|
||||
}
|
||||
|
||||
public void showTextInputDialog(String hint, String current, int editType) {
|
||||
runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
|
||||
}
|
||||
|
@ -265,4 +253,8 @@ public class GameActivity extends NativeActivity {
|
|||
|
||||
return langCode;
|
||||
}
|
||||
|
||||
public boolean hasPhysicalKeyboard() {
|
||||
return getContext().getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
|
||||
interface HIDDevice
|
||||
{
|
||||
public int getId();
|
||||
public int getVendorId();
|
||||
public int getProductId();
|
||||
public String getSerialNumber();
|
||||
public int getVersion();
|
||||
public String getManufacturerName();
|
||||
public String getProductName();
|
||||
public UsbDevice getDevice();
|
||||
public boolean open();
|
||||
public int sendFeatureReport(byte[] report);
|
||||
public int sendOutputReport(byte[] report);
|
||||
public boolean getFeatureReport(byte[] report);
|
||||
public void setFrozen(boolean frozen);
|
||||
public void close();
|
||||
public void shutdown();
|
||||
}
|
|
@ -0,0 +1,650 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.os.*;
|
||||
|
||||
//import com.android.internal.util.HexDump;
|
||||
|
||||
import java.lang.Runnable;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
private HIDDeviceManager mManager;
|
||||
private BluetoothDevice mDevice;
|
||||
private int mDeviceId;
|
||||
private BluetoothGatt mGatt;
|
||||
private boolean mIsRegistered = false;
|
||||
private boolean mIsConnected = false;
|
||||
private boolean mIsChromebook = false;
|
||||
private boolean mIsReconnecting = false;
|
||||
private boolean mFrozen = false;
|
||||
private LinkedList<GattOperation> mOperations;
|
||||
GattOperation mCurrentOperation = null;
|
||||
private Handler mHandler;
|
||||
|
||||
private static final int TRANSPORT_AUTO = 0;
|
||||
private static final int TRANSPORT_BREDR = 1;
|
||||
private static final int TRANSPORT_LE = 2;
|
||||
|
||||
private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
|
||||
|
||||
static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
|
||||
static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
|
||||
static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
|
||||
|
||||
static class GattOperation {
|
||||
private enum Operation {
|
||||
CHR_READ,
|
||||
CHR_WRITE,
|
||||
ENABLE_NOTIFICATION
|
||||
}
|
||||
|
||||
Operation mOp;
|
||||
UUID mUuid;
|
||||
byte[] mValue;
|
||||
BluetoothGatt mGatt;
|
||||
boolean mResult = true;
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
}
|
||||
|
||||
private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
|
||||
mGatt = gatt;
|
||||
mOp = operation;
|
||||
mUuid = uuid;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// This is executed in main thread
|
||||
BluetoothGattCharacteristic chr;
|
||||
|
||||
switch (mOp) {
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing descriptor of " + chr.getUuid());
|
||||
if (chr != null) {
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
int properties = chr.getProperties();
|
||||
byte[] value;
|
||||
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
} else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
} else {
|
||||
Log.e(TAG, "Unable to start notifications on input characteristic");
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean finish() {
|
||||
return mResult;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
|
||||
BluetoothGattService valveService = mGatt.getService(steamControllerService);
|
||||
if (valveService == null)
|
||||
return null;
|
||||
return valveService.getCharacteristic(uuid);
|
||||
}
|
||||
|
||||
static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.CHR_READ, uuid);
|
||||
}
|
||||
|
||||
static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
|
||||
return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
|
||||
}
|
||||
|
||||
static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
|
||||
return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
|
||||
mManager = manager;
|
||||
mDevice = device;
|
||||
mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mIsRegistered = false;
|
||||
mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
mOperations = new LinkedList<GattOperation>();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
mGatt = connectGatt();
|
||||
// final HIDDeviceBLESteamController finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.checkConnectionForChromebookIssue();
|
||||
// }
|
||||
// }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
public BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
}
|
||||
|
||||
private BluetoothGatt connectGatt() {
|
||||
return connectGatt(false);
|
||||
}
|
||||
|
||||
protected int getConnectionState() {
|
||||
|
||||
Context context = mManager.getContext();
|
||||
if (context == null) {
|
||||
// We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (btManager == null) {
|
||||
// This device doesn't support Bluetooth. We should never be here, because how did
|
||||
// we instantiate a device to start with?
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void checkConnectionForChromebookIssue() {
|
||||
if (!mIsChromebook) {
|
||||
// We only do this on Chromebooks, because otherwise it's really annoying to just attempt
|
||||
// over and over.
|
||||
return;
|
||||
}
|
||||
|
||||
int connectionState = getConnectionState();
|
||||
|
||||
switch (connectionState) {
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
if (!mIsConnected) {
|
||||
// We are in the Bad Chromebook Place. We can force a disconnect
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
else if (!isRegistered()) {
|
||||
if (mGatt.getServices().size() > 0) {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
|
||||
probeService(this);
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
|
||||
break;
|
||||
}
|
||||
|
||||
final HIDDeviceBLESteamController finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.checkConnectionForChromebookIssue();
|
||||
}
|
||||
}, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
private boolean isRegistered() {
|
||||
return mIsRegistered;
|
||||
}
|
||||
|
||||
private void setRegistered() {
|
||||
mIsRegistered = true;
|
||||
}
|
||||
|
||||
private boolean probeService(HIDDeviceBLESteamController controller) {
|
||||
|
||||
if (isRegistered()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mIsConnected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.v(TAG, "probeService controller=" + controller);
|
||||
|
||||
for (BluetoothGattService service : mGatt.getServices()) {
|
||||
if (service.getUuid().equals(steamControllerService)) {
|
||||
Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
|
||||
|
||||
for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
Log.v(TAG, "Found input characteristic");
|
||||
// Start notifications
|
||||
BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
|
||||
if (cccd != null) {
|
||||
enableNotification(chr.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void finishCurrentGattOperation() {
|
||||
GattOperation op = null;
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null) {
|
||||
op = mCurrentOperation;
|
||||
mCurrentOperation = null;
|
||||
}
|
||||
}
|
||||
if (op != null) {
|
||||
boolean result = op.finish(); // TODO: Maybe in main thread as well?
|
||||
|
||||
// Our operation failed, let's add it back to the beginning of our queue.
|
||||
if (!result) {
|
||||
mOperations.addFirst(op);
|
||||
}
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void executeNextGattOperation() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation != null)
|
||||
return;
|
||||
|
||||
if (mOperations.isEmpty())
|
||||
return;
|
||||
|
||||
mCurrentOperation = mOperations.removeFirst();
|
||||
}
|
||||
|
||||
// Run in main thread
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mOperations) {
|
||||
if (mCurrentOperation == null) {
|
||||
Log.e(TAG, "Current operation null in executor?");
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentOperation.run();
|
||||
// now wait for the GATT callback and when it comes, finish this operation
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void queueGattOperation(GattOperation op) {
|
||||
synchronized (mOperations) {
|
||||
mOperations.add(op);
|
||||
}
|
||||
executeNextGattOperation();
|
||||
}
|
||||
|
||||
private void enableNotification(UUID chrUuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void writeCharacteristic(UUID uuid, byte[] value) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
public void readCharacteristic(UUID uuid) {
|
||||
GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
|
||||
queueGattOperation(op);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////// BluetoothGattCallback overridden methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
|
||||
//Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
|
||||
mIsReconnecting = false;
|
||||
if (newState == 2) {
|
||||
mIsConnected = true;
|
||||
// Run directly, without GattOperation
|
||||
if (!isRegistered()) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
// Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
|
||||
}
|
||||
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onServicesDiscovered status=" + status);
|
||||
if (status == 0) {
|
||||
if (gatt.getServices().size() == 0) {
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
probeService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
//Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
|
||||
|
||||
if (characteristic.getUuid().equals(reportCharacteristic)) {
|
||||
// Only register controller with the native side once it has been fully configured
|
||||
if (!isRegistered()) {
|
||||
Log.v(TAG, "Registering Steam Controller with ID: " + getId());
|
||||
mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);
|
||||
setRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
// Enable this for verbose logging of controller input reports
|
||||
//Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
|
||||
|
||||
if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
|
||||
mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
//Log.v(TAG, "onDescriptorRead status=" + status);
|
||||
}
|
||||
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
|
||||
//Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
|
||||
|
||||
if (chr.getUuid().equals(inputCharacteristic)) {
|
||||
boolean hasWrittenInputDescriptor = true;
|
||||
BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
}
|
||||
}
|
||||
|
||||
finishCurrentGattOperation();
|
||||
}
|
||||
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
//Log.v(TAG, "onReliableWriteCompleted status=" + status);
|
||||
}
|
||||
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
//Log.v(TAG, "onReadRemoteRssi status=" + status);
|
||||
}
|
||||
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
//Log.v(TAG, "onMtuChanged status=" + status);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////// Public API
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
// Valve Corporation
|
||||
final int VALVE_USB_VID = 0x28DE;
|
||||
return VALVE_USB_VID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
// We don't have an easy way to query from the Bluetooth device, but we know what it is
|
||||
final int D0G_BLE2_PID = 0x1106;
|
||||
return D0G_BLE2_PID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
// This will be read later via feature report by Steam
|
||||
return "12345";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
return "Valve Corporation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
return "Steam Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We need to skip the first byte, as that doesn't go over the air
|
||||
byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
|
||||
writeCharacteristic(reportCharacteristic, actual_report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
|
||||
writeCharacteristic(reportCharacteristic, report);
|
||||
return report.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
if (!isRegistered()) {
|
||||
Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
|
||||
if (mIsConnected) {
|
||||
probeService(this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Log.v(TAG, "getFeatureReport");
|
||||
readCharacteristic(reportCharacteristic);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
mIsRegistered = false;
|
||||
mIsConnected = false;
|
||||
mOperations.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,691 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class HIDDeviceManager {
|
||||
private static final String TAG = "hidapi";
|
||||
private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
|
||||
|
||||
private static HIDDeviceManager sManager;
|
||||
private static int sManagerRefCount = 0;
|
||||
|
||||
public static HIDDeviceManager acquire(Context context) {
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager = new HIDDeviceManager(context);
|
||||
}
|
||||
++sManagerRefCount;
|
||||
return sManager;
|
||||
}
|
||||
|
||||
public static void release(HIDDeviceManager manager) {
|
||||
if (manager == sManager) {
|
||||
--sManagerRefCount;
|
||||
if (sManagerRefCount == 0) {
|
||||
sManager.close();
|
||||
sManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
|
||||
private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
|
||||
private int mNextDeviceId = 0;
|
||||
private SharedPreferences mSharedPreferences = null;
|
||||
private boolean mIsChromebook = false;
|
||||
private UsbManager mUsbManager;
|
||||
private Handler mHandler;
|
||||
private BluetoothManager mBluetoothManager;
|
||||
private List<BluetoothDevice> mLastBluetoothDevices;
|
||||
|
||||
private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDeviceDetached(usbDevice);
|
||||
} else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
|
||||
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||
handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
// Bluetooth device was connected. If it was a Steam Controller, handle it
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device connected: " + device);
|
||||
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth device was disconnected, remove from controller manager (if any)
|
||||
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
Log.d(TAG, "Bluetooth device disconnected: " + device);
|
||||
|
||||
disconnectBluetoothDevice(device);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private HIDDeviceManager(final Context context) {
|
||||
mContext = context;
|
||||
|
||||
HIDDeviceRegisterCallback();
|
||||
|
||||
mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
|
||||
mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
|
||||
|
||||
// if (shouldClear) {
|
||||
// SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
// spedit.clear();
|
||||
// spedit.commit();
|
||||
// }
|
||||
// else
|
||||
{
|
||||
mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public int getDeviceIDForIdentifier(String identifier) {
|
||||
SharedPreferences.Editor spedit = mSharedPreferences.edit();
|
||||
|
||||
int result = mSharedPreferences.getInt(identifier, 0);
|
||||
if (result == 0) {
|
||||
result = mNextDeviceId++;
|
||||
spedit.putInt("next_device_id", mNextDeviceId);
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void initializeUSB() {
|
||||
mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
|
||||
if (mUsbManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// Logging
|
||||
for (UsbDevice device : mUsbManager.getDeviceList().values()) {
|
||||
Log.i(TAG,"Path: " + device.getDeviceName());
|
||||
Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
|
||||
Log.i(TAG,"Product: " + device.getProductName());
|
||||
Log.i(TAG,"ID: " + device.getDeviceId());
|
||||
Log.i(TAG,"Class: " + device.getDeviceClass());
|
||||
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
|
||||
Log.i(TAG,"Vendor ID " + device.getVendorId());
|
||||
Log.i(TAG,"Product ID: " + device.getProductId());
|
||||
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
|
||||
Log.i(TAG,"---------------------------------------");
|
||||
|
||||
// Get interface details
|
||||
for (int index = 0; index < device.getInterfaceCount(); index++) {
|
||||
UsbInterface mUsbInterface = device.getInterface(index);
|
||||
Log.i(TAG," ***** *****");
|
||||
Log.i(TAG," Interface index: " + index);
|
||||
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
|
||||
Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
|
||||
Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
Log.i(TAG," ++++ ++++ ++++");
|
||||
Log.i(TAG," Endpoint index: " + epi);
|
||||
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
|
||||
Log.i(TAG," Direction: " + mEndpoint.getDirection());
|
||||
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
|
||||
Log.i(TAG," Interval: " + mEndpoint.getInterval());
|
||||
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
|
||||
Log.i(TAG," Type: " + mEndpoint.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.i(TAG," No more devices connected.");
|
||||
*/
|
||||
|
||||
// Register for USB broadcasts and permission completions
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager getUSBManager() {
|
||||
return mUsbManager;
|
||||
}
|
||||
|
||||
private void shutdownUSB() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mUsbBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
|
||||
return true;
|
||||
}
|
||||
if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB360_IFACE_SUBCLASS = 93;
|
||||
final int XB360_IFACE_PROTOCOL = 1; // Wired
|
||||
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x0079, // GPD Win 2
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x046d, // Logitech
|
||||
0x056e, // Elecom
|
||||
0x06a3, // Saitek
|
||||
0x0738, // Mad Catz
|
||||
0x07ff, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x1038, // SteelSeries
|
||||
0x11c9, // Nacon
|
||||
0x12ab, // Unknown
|
||||
0x1430, // RedOctane
|
||||
0x146b, // BigBen
|
||||
0x1532, // Razer Sabertooth
|
||||
0x15e4, // Numark
|
||||
0x162e, // Joytech
|
||||
0x1689, // Razer Onza
|
||||
0x1949, // Lab126, Inc.
|
||||
0x1bad, // Harmonix
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
|
||||
(usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||
|
||||
usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
|
||||
usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
|
||||
int vendor_id = usbDevice.getVendorId();
|
||||
for (int supportedVid : SUPPORTED_VENDORS) {
|
||||
if (vendor_id == supportedVid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleUsbDeviceAttached(UsbDevice usbDevice) {
|
||||
connectHIDDeviceUSB(usbDevice);
|
||||
}
|
||||
|
||||
private void handleUsbDeviceDetached(UsbDevice usbDevice) {
|
||||
List<Integer> devices = new ArrayList<Integer>();
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
devices.add(device.getId());
|
||||
}
|
||||
}
|
||||
for (int id : devices) {
|
||||
HIDDevice device = mDevicesById.get(id);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
if (usbDevice.equals(device.getDevice())) {
|
||||
boolean opened = false;
|
||||
if (permission_granted) {
|
||||
opened = device.open();
|
||||
}
|
||||
HIDDeviceOpenResult(device.getId(), opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connectHIDDeviceUSB(UsbDevice usbDevice) {
|
||||
synchronized (this) {
|
||||
int interface_mask = 0;
|
||||
for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {
|
||||
UsbInterface usbInterface = usbDevice.getInterface(interface_index);
|
||||
if (isHIDDeviceInterface(usbDevice, usbInterface)) {
|
||||
// Check to see if we've already added this interface
|
||||
// This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive
|
||||
int interface_id = usbInterface.getId();
|
||||
if ((interface_mask & (1 << interface_id)) != 0) {
|
||||
continue;
|
||||
}
|
||||
interface_mask |= (1 << interface_id);
|
||||
|
||||
HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);
|
||||
int id = device.getId();
|
||||
mDevicesById.put(id, device);
|
||||
HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find bonded bluetooth controllers and create SteamControllers for them
|
||||
mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (mBluetoothManager == null) {
|
||||
// This device doesn't support Bluetooth.
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
|
||||
if (btAdapter == null) {
|
||||
// This device has Bluetooth support in the codebase, but has no available adapters.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get our bonded devices.
|
||||
for (BluetoothDevice device : btAdapter.getBondedDevices()) {
|
||||
|
||||
Log.d(TAG, "Bluetooth device available: " + device);
|
||||
if (isSteamController(device)) {
|
||||
connectBluetoothDevice(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NOTE: These don't work on Chromebooks, to my undying dismay.
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mContext.registerReceiver(mBluetoothBroadcast, filter);
|
||||
|
||||
if (mIsChromebook) {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLastBluetoothDevices = new ArrayList<BluetoothDevice>();
|
||||
|
||||
// final HIDDeviceManager finalThis = this;
|
||||
// mHandler.postDelayed(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// finalThis.chromebookConnectionHandler();
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownBluetooth() {
|
||||
try {
|
||||
mContext.unregisterReceiver(mBluetoothBroadcast);
|
||||
} catch (Exception e) {
|
||||
// We may not have registered, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
// Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
|
||||
// This function provides a sort of dummy version of that, watching for changes in the
|
||||
// connected devices and attempting to add controllers as things change.
|
||||
public void chromebookConnectionHandler() {
|
||||
if (!mIsChromebook) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
connected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
|
||||
if (!currentConnected.contains(bluetoothDevice)) {
|
||||
disconnected.add(bluetoothDevice);
|
||||
}
|
||||
}
|
||||
|
||||
mLastBluetoothDevices = currentConnected;
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : disconnected) {
|
||||
disconnectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
for (BluetoothDevice bluetoothDevice : connected) {
|
||||
connectBluetoothDevice(bluetoothDevice);
|
||||
}
|
||||
|
||||
final HIDDeviceManager finalThis = this;
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finalThis.chromebookConnectionHandler();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
|
||||
synchronized (this) {
|
||||
if (mBluetoothDevices.containsKey(bluetoothDevice)) {
|
||||
Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
|
||||
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
device.reconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.put(bluetoothDevice, device);
|
||||
mDevicesById.put(id, device);
|
||||
|
||||
// The Steam Controller will mark itself connected once initialization is complete
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
|
||||
synchronized (this) {
|
||||
HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
int id = device.getId();
|
||||
mBluetoothDevices.remove(bluetoothDevice);
|
||||
mDevicesById.remove(id);
|
||||
device.shutdown();
|
||||
HIDDeviceDisconnected(id);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSteamController(BluetoothDevice bluetoothDevice) {
|
||||
// Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
|
||||
if (bluetoothDevice == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
shutdownUSB();
|
||||
shutdownBluetooth();
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.shutdown();
|
||||
}
|
||||
mDevicesById.clear();
|
||||
mBluetoothDevices.clear();
|
||||
HIDDeviceReleaseCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrozen(boolean frozen) {
|
||||
synchronized (this) {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private HIDDevice getDevice(int id) {
|
||||
synchronized (this) {
|
||||
HIDDevice result = mDevicesById.get(id);
|
||||
if (result == null) {
|
||||
Log.v(TAG, "No device for id: " + id);
|
||||
Log.v(TAG, "Available devices: " + mDevicesById.keySet());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////// JNI interface functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean initialize(boolean usb, boolean bluetooth) {
|
||||
Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
|
||||
|
||||
if (usb) {
|
||||
initializeUSB();
|
||||
}
|
||||
if (bluetooth) {
|
||||
initializeBluetooth();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean openDevice(int deviceID) {
|
||||
Log.v(TAG, "openDevice deviceID=" + deviceID);
|
||||
HIDDevice device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look to see if this is a USB device and we have permission to access it
|
||||
UsbDevice usbDevice = device.getDevice();
|
||||
if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {
|
||||
HIDDeviceOpenPending(deviceID);
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return device.open();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sendOutputReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendOutputReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int sendFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return device.sendFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean getFeatureReport(int deviceID, byte[] report) {
|
||||
try {
|
||||
//Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return false;
|
||||
}
|
||||
|
||||
return device.getFeatureReport(report);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void closeDevice(int deviceID) {
|
||||
try {
|
||||
Log.v(TAG, "closeDevice deviceID=" + deviceID);
|
||||
HIDDevice device;
|
||||
device = getDevice(deviceID);
|
||||
if (device == null) {
|
||||
HIDDeviceDisconnected(deviceID);
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////// Native methods
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private native void HIDDeviceRegisterCallback();
|
||||
private native void HIDDeviceReleaseCallback();
|
||||
|
||||
native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
|
||||
native void HIDDeviceOpenPending(int deviceID);
|
||||
native void HIDDeviceOpenResult(int deviceID, boolean opened);
|
||||
native void HIDDeviceDisconnected(int deviceID);
|
||||
|
||||
native void HIDDeviceInputReport(int deviceID, byte[] report);
|
||||
native void HIDDeviceFeatureReport(int deviceID, byte[] report);
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.hardware.usb.*;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import java.util.Arrays;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
private static final String TAG = "hidapi";
|
||||
|
||||
protected HIDDeviceManager mManager;
|
||||
protected UsbDevice mDevice;
|
||||
protected int mInterfaceIndex;
|
||||
protected int mInterface;
|
||||
protected int mDeviceId;
|
||||
protected UsbDeviceConnection mConnection;
|
||||
protected UsbEndpoint mInputEndpoint;
|
||||
protected UsbEndpoint mOutputEndpoint;
|
||||
protected InputThread mInputThread;
|
||||
protected boolean mRunning;
|
||||
protected boolean mFrozen;
|
||||
|
||||
public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {
|
||||
mManager = manager;
|
||||
mDevice = usbDevice;
|
||||
mInterfaceIndex = interface_index;
|
||||
mInterface = mDevice.getInterface(mInterfaceIndex).getId();
|
||||
mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mDeviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId() {
|
||||
return mDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProductId() {
|
||||
return mDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
catch (SecurityException exception) {
|
||||
//Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getVendorId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
result = String.format("%x", getProductId());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsbDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public String getDeviceName() {
|
||||
return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() {
|
||||
mConnection = mManager.getUSBManager().openDevice(mDevice);
|
||||
if (mConnection == null) {
|
||||
Log.w(TAG, "Unable to open USB device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Force claim our interface
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
if (!mConnection.claimInterface(iface, true)) {
|
||||
Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the endpoints
|
||||
for (int j = 0; j < iface.getEndpointCount(); j++) {
|
||||
UsbEndpoint endpt = iface.getEndpoint(j);
|
||||
switch (endpt.getDirection()) {
|
||||
case UsbConstants.USB_DIR_IN:
|
||||
if (mInputEndpoint == null) {
|
||||
mInputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
case UsbConstants.USB_DIR_OUT:
|
||||
if (mOutputEndpoint == null) {
|
||||
mOutputEndpoint = endpt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the required endpoints were present
|
||||
if (mInputEndpoint == null || mOutputEndpoint == null) {
|
||||
Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening for input
|
||||
mRunning = true;
|
||||
mInputThread = new InputThread();
|
||||
mInputThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
|
||||
0x09/*HID set_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendOutputReport(byte[] report) {
|
||||
int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
|
||||
if (r != report.length) {
|
||||
Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeatureReport(byte[] report) {
|
||||
int res = -1;
|
||||
int offset = 0;
|
||||
int length = report.length;
|
||||
boolean skipped_report_id = false;
|
||||
byte report_number = report[0];
|
||||
|
||||
if (report_number == 0x0) {
|
||||
/* Offset the return buffer by 1, so that the report ID
|
||||
will remain in byte 0. */
|
||||
++offset;
|
||||
--length;
|
||||
skipped_report_id = true;
|
||||
}
|
||||
|
||||
res = mConnection.controlTransfer(
|
||||
UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
|
||||
0x01/*HID get_report*/,
|
||||
(3/*HID feature*/ << 8) | report_number,
|
||||
mInterface,
|
||||
report, offset, length,
|
||||
1000/*timeout millis*/);
|
||||
|
||||
if (res < 0) {
|
||||
Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (skipped_report_id) {
|
||||
++res;
|
||||
++length;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (res == length) {
|
||||
data = report;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(report, 0, res);
|
||||
}
|
||||
mManager.HIDDeviceFeatureReport(mDeviceId, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mRunning = false;
|
||||
if (mInputThread != null) {
|
||||
while (mInputThread.isAlive()) {
|
||||
mInputThread.interrupt();
|
||||
try {
|
||||
mInputThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// Keep trying until we're done
|
||||
}
|
||||
}
|
||||
mInputThread = null;
|
||||
}
|
||||
if (mConnection != null) {
|
||||
UsbInterface iface = mDevice.getInterface(mInterfaceIndex);
|
||||
mConnection.releaseInterface(iface);
|
||||
mConnection.close();
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
close();
|
||||
mManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrozen(boolean frozen) {
|
||||
mFrozen = frozen;
|
||||
}
|
||||
|
||||
protected class InputThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
int packetSize = mInputEndpoint.getMaxPacketSize();
|
||||
byte[] packet = new byte[packetSize];
|
||||
while (mRunning) {
|
||||
int r;
|
||||
try
|
||||
{
|
||||
r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
|
||||
break;
|
||||
}
|
||||
if (r < 0) {
|
||||
// Could be a timeout or an I/O error
|
||||
}
|
||||
if (r > 0) {
|
||||
byte[] data;
|
||||
if (r == packetSize) {
|
||||
data = packet;
|
||||
} else {
|
||||
data = Arrays.copyOfRange(packet, 0, r);
|
||||
}
|
||||
|
||||
if (!mFrozen) {
|
||||
mManager.HIDDeviceInputReport(mDeviceId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
SDL library initialization
|
||||
*/
|
||||
public class SDL {
|
||||
|
||||
// This function should be called first and sets up the native code
|
||||
// so it can call into the Java classes
|
||||
public static void setupJNI() {
|
||||
SDLActivity.nativeSetupJNI();
|
||||
SDLAudioManager.nativeSetupJNI();
|
||||
SDLControllerManager.nativeSetupJNI();
|
||||
}
|
||||
|
||||
// This function should be called each time the activity is started
|
||||
public static void initialize() {
|
||||
setContext(null);
|
||||
|
||||
SDLActivity.initialize();
|
||||
SDLAudioManager.initialize();
|
||||
SDLControllerManager.initialize();
|
||||
}
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Let's see if we have ReLinker available in the project. This is necessary for
|
||||
// some projects that have huge numbers of local libraries bundled, and thus may
|
||||
// trip a bug in Android's native library loader which ReLinker works around. (If
|
||||
// loadLibrary works properly, ReLinker will simply use the normal Android method
|
||||
// internally.)
|
||||
//
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
Method forceMethod = relinkClass.getDeclaredMethod("force");
|
||||
Object relinkInstance = forceMethod.invoke(null);
|
||||
Class<?> relinkInstanceClass = relinkInstance.getClass();
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
try {
|
||||
System.loadLibrary(libraryName);
|
||||
}
|
||||
catch (final UnsatisfiedLinkError ule) {
|
||||
throw ule;
|
||||
}
|
||||
catch (final SecurityException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Context mContext;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,514 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
sampleRate = 48000;
|
||||
}
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
}
|
||||
switch (audioFormat)
|
||||
{
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
sampleSize = 1;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
sampleSize = 2;
|
||||
break;
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
sampleSize = 4;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
sampleSize = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isCapture) {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_IN_STEREO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (desiredChannels) {
|
||||
case 1:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
break;
|
||||
case 7:
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
desiredChannels = 6;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
|
||||
desiredChannels = 2;
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Log.v(TAG, "Speaker configuration (and order of channels):");
|
||||
|
||||
if ((channelConfig & 0x00000004) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000008) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000010) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000020) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
|
||||
}
|
||||
if ((channelConfig & 0x00000040) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00000080) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
|
||||
}
|
||||
if ((channelConfig & 0x00000100) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000200) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000400) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
|
||||
}
|
||||
if ((channelConfig & 0x00000800) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
|
||||
}
|
||||
if ((channelConfig & 0x00001000) != 0) {
|
||||
Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
|
||||
}
|
||||
*/
|
||||
}
|
||||
frameSize = (sampleSize * desiredChannels);
|
||||
|
||||
// Let the user pick a larger buffer if they really want -- but ye
|
||||
// gods they probably shouldn't, the minimums are horrifyingly high
|
||||
// latency already
|
||||
int minBufferSize;
|
||||
if (isCapture) {
|
||||
minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
} else {
|
||||
minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
|
||||
}
|
||||
desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
|
||||
|
||||
int[] results = new int[4];
|
||||
|
||||
if (isCapture) {
|
||||
if (mAudioRecord == null) {
|
||||
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
|
||||
channelConfig, audioFormat, desiredFrames * frameSize);
|
||||
|
||||
// see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
|
||||
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.e(TAG, "Failed during initialization of AudioRecord");
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
results[0] = mAudioRecord.getSampleRate();
|
||||
results[1] = mAudioRecord.getAudioFormat();
|
||||
results[2] = mAudioRecord.getChannelCount();
|
||||
|
||||
} else {
|
||||
if (mAudioTrack == null) {
|
||||
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
|
||||
|
||||
// Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
|
||||
// Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
|
||||
// Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
|
||||
if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
|
||||
/* Try again, with safer values */
|
||||
|
||||
Log.e(TAG, "Failed during initialization of Audio Track");
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
results[0] = mAudioTrack.getSampleRate();
|
||||
results[1] = mAudioTrack.getAudioFormat();
|
||||
results[2] = mAudioTrack.getChannelCount();
|
||||
}
|
||||
results[3] = desiredFrames;
|
||||
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteFloatBuffer(float[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(float)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteShortBuffer(short[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(short)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void audioWriteByteBuffer(byte[] buffer) {
|
||||
if (mAudioTrack == null) {
|
||||
Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length; ) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i);
|
||||
if (result > 0) {
|
||||
i += result;
|
||||
} else if (result == 0) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch(InterruptedException e) {
|
||||
// Nom nom
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SDL audio: error return from write(byte)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioClose() {
|
||||
if (mAudioTrack != null) {
|
||||
mAudioTrack.stop();
|
||||
mAudioTrack.release();
|
||||
mAudioTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void captureClose() {
|
||||
if (mAudioRecord != null) {
|
||||
mAudioRecord.stop();
|
||||
mAudioRecord.release();
|
||||
mAudioRecord = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static void audioSetThreadPriority(boolean iscapture, int device_id) {
|
||||
try {
|
||||
|
||||
/* Set thread name */
|
||||
if (iscapture) {
|
||||
Thread.currentThread().setName("SDLAudioC" + device_id);
|
||||
} else {
|
||||
Thread.currentThread().setName("SDLAudioP" + device_id);
|
||||
}
|
||||
|
||||
/* Set thread priority */
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "modify thread properties failed " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
|
@ -0,0 +1,854 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
public class SDLControllerManager
|
||||
{
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
public static native int onNativePadDown(int device_id, int keycode);
|
||||
public static native int onNativePadUp(int device_id, int keycode);
|
||||
public static native void onNativeJoy(int device_id, int axis,
|
||||
float value);
|
||||
public static native void onNativeHat(int device_id, int hat_id,
|
||||
int x, int y);
|
||||
|
||||
protected static SDLJoystickHandler mJoystickHandler;
|
||||
protected static SDLHapticHandler mHapticHandler;
|
||||
|
||||
private static final String TAG = "SDLControllerManager";
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
}
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
|
||||
public static boolean handleJoystickMotionEvent(MotionEvent event) {
|
||||
return mJoystickHandler.handleMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollInputDevices() {
|
||||
mJoystickHandler.pollInputDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void pollHapticDevices() {
|
||||
mHapticHandler.pollHapticDevices();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticRun(int device_id, float intensity, int length) {
|
||||
mHapticHandler.run(device_id, intensity, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void hapticStop(int device_id)
|
||||
{
|
||||
mHapticHandler.stop(device_id);
|
||||
}
|
||||
|
||||
// Check if a given device is considered a possible SDL joystick
|
||||
public static boolean isDeviceSDLJoystick(int deviceId) {
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
// We cannot use InputDevice.isVirtual before API 16, so let's accept
|
||||
// only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
|
||||
if ((device == null) || (deviceId < 0)) {
|
||||
return false;
|
||||
}
|
||||
int sources = device.getSources();
|
||||
|
||||
/* This is called for every button press, so let's not spam the logs */
|
||||
/*
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
|
||||
}
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
|
||||
Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
|
||||
}
|
||||
*/
|
||||
|
||||
return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
|
||||
((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
|
||||
((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLJoystickHandler {
|
||||
|
||||
/**
|
||||
* Handles given MotionEvent.
|
||||
* @param event the event to be handled.
|
||||
* @return if given event was processed.
|
||||
*/
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding and removing of input devices.
|
||||
*/
|
||||
public void pollInputDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Actual joystick functionality available for API >= 12 devices */
|
||||
class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
|
||||
static class SDLJoystick {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public String desc;
|
||||
public ArrayList<InputDevice.MotionRange> axes;
|
||||
public ArrayList<InputDevice.MotionRange> hats;
|
||||
}
|
||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||
@Override
|
||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||
// Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
|
||||
int arg0Axis = arg0.getAxis();
|
||||
int arg1Axis = arg1.getAxis();
|
||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg0Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
|
||||
private final ArrayList<SDLJoystick> mJoysticks;
|
||||
|
||||
public SDLJoystickHandler_API16() {
|
||||
|
||||
mJoysticks = new ArrayList<SDLJoystick>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollInputDevices() {
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
|
||||
for (int device_id : deviceIds) {
|
||||
if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
|
||||
SDLJoystick joystick = getJoystick(device_id);
|
||||
if (joystick == null) {
|
||||
InputDevice joystickDevice = InputDevice.getDevice(device_id);
|
||||
joystick = new SDLJoystick();
|
||||
joystick.device_id = device_id;
|
||||
joystick.name = joystickDevice.getName();
|
||||
joystick.desc = getJoystickDescriptor(joystickDevice);
|
||||
joystick.axes = new ArrayList<InputDevice.MotionRange>();
|
||||
joystick.hats = new ArrayList<InputDevice.MotionRange>();
|
||||
|
||||
List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
|
||||
Collections.sort(ranges, new RangeComparator());
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||
joystick.hats.add(range);
|
||||
} else {
|
||||
joystick.axes.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
int device_id = joystick.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveJoystick(device_id);
|
||||
for (int i = 0; i < mJoysticks.size(); i++) {
|
||||
if (mJoysticks.get(i).device_id == device_id) {
|
||||
mJoysticks.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLJoystick getJoystick(int device_id) {
|
||||
for (SDLJoystick joystick : mJoysticks) {
|
||||
if (joystick.device_id == device_id) {
|
||||
return joystick;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMotionEvent(MotionEvent event) {
|
||||
int actionPointerIndex = event.getActionIndex();
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
SDLJoystick joystick = getJoystick(event.getDeviceId());
|
||||
if (joystick != null) {
|
||||
for (int i = 0; i < joystick.axes.size(); i++) {
|
||||
InputDevice.MotionRange range = joystick.axes.get(i);
|
||||
/* Normalize the value to -1...1 */
|
||||
float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||
SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
|
||||
}
|
||||
for (int i = 0; i < joystick.hats.size() / 2; i++) {
|
||||
int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
|
||||
int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
|
||||
SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoystickDescriptor(InputDevice joystickDevice) {
|
||||
String desc = joystickDevice.getDescriptor();
|
||||
|
||||
if (desc != null && !desc.isEmpty()) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
return joystickDevice.getName();
|
||||
}
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
|
||||
@Override
|
||||
public int getProductId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getProductId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
int[] keys = new int[] {
|
||||
KeyEvent.KEYCODE_BUTTON_A,
|
||||
KeyEvent.KEYCODE_BUTTON_B,
|
||||
KeyEvent.KEYCODE_BUTTON_X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y,
|
||||
KeyEvent.KEYCODE_BACK,
|
||||
KeyEvent.KEYCODE_MENU,
|
||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||
KeyEvent.KEYCODE_BUTTON_START,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||
KeyEvent.KEYCODE_BUTTON_L1,
|
||||
KeyEvent.KEYCODE_BUTTON_R1,
|
||||
KeyEvent.KEYCODE_DPAD_UP,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||
|
||||
// These don't map into any SDL controller buttons directly
|
||||
KeyEvent.KEYCODE_BUTTON_L2,
|
||||
KeyEvent.KEYCODE_BUTTON_R2,
|
||||
KeyEvent.KEYCODE_BUTTON_C,
|
||||
KeyEvent.KEYCODE_BUTTON_Z,
|
||||
KeyEvent.KEYCODE_BUTTON_1,
|
||||
KeyEvent.KEYCODE_BUTTON_2,
|
||||
KeyEvent.KEYCODE_BUTTON_3,
|
||||
KeyEvent.KEYCODE_BUTTON_4,
|
||||
KeyEvent.KEYCODE_BUTTON_5,
|
||||
KeyEvent.KEYCODE_BUTTON_6,
|
||||
KeyEvent.KEYCODE_BUTTON_7,
|
||||
KeyEvent.KEYCODE_BUTTON_8,
|
||||
KeyEvent.KEYCODE_BUTTON_9,
|
||||
KeyEvent.KEYCODE_BUTTON_10,
|
||||
KeyEvent.KEYCODE_BUTTON_11,
|
||||
KeyEvent.KEYCODE_BUTTON_12,
|
||||
KeyEvent.KEYCODE_BUTTON_13,
|
||||
KeyEvent.KEYCODE_BUTTON_14,
|
||||
KeyEvent.KEYCODE_BUTTON_15,
|
||||
KeyEvent.KEYCODE_BUTTON_16,
|
||||
};
|
||||
int[] masks = new int[] {
|
||||
(1 << 0), // A -> A
|
||||
(1 << 1), // B -> B
|
||||
(1 << 2), // X -> X
|
||||
(1 << 3), // Y -> Y
|
||||
(1 << 4), // BACK -> BACK
|
||||
(1 << 6), // MENU -> START
|
||||
(1 << 5), // MODE -> GUIDE
|
||||
(1 << 6), // START -> START
|
||||
(1 << 7), // THUMBL -> LEFTSTICK
|
||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||
(1 << 9), // L1 -> LEFTSHOULDER
|
||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||
(1 << 4), // SELECT -> BACK
|
||||
(1 << 0), // DPAD_CENTER -> A
|
||||
(1 << 15), // L2 -> ??
|
||||
(1 << 16), // R2 -> ??
|
||||
(1 << 17), // C -> ??
|
||||
(1 << 18), // Z -> ??
|
||||
(1 << 20), // 1 -> ??
|
||||
(1 << 21), // 2 -> ??
|
||||
(1 << 22), // 3 -> ??
|
||||
(1 << 23), // 4 -> ??
|
||||
(1 << 24), // 5 -> ??
|
||||
(1 << 25), // 6 -> ??
|
||||
(1 << 26), // 7 -> ??
|
||||
(1 << 27), // 8 -> ??
|
||||
(1 << 28), // 9 -> ??
|
||||
(1 << 29), // 10 -> ??
|
||||
(1 << 30), // 11 -> ??
|
||||
(1 << 31), // 12 -> ??
|
||||
// We're out of room...
|
||||
0xFFFFFFFF, // 13 -> ??
|
||||
0xFFFFFFFF, // 14 -> ??
|
||||
0xFFFFFFFF, // 15 -> ??
|
||||
0xFFFFFFFF, // 16 -> ??
|
||||
};
|
||||
boolean[] has_keys = joystickDevice.hasKeys(keys);
|
||||
for (int i = 0; i < keys.length; ++i) {
|
||||
if (has_keys[i]) {
|
||||
button_mask |= masks[i];
|
||||
}
|
||||
}
|
||||
return button_mask;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler_API26 extends SDLHapticHandler {
|
||||
@Override
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
|
||||
if (intensity == 0.0f) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int vibeValue = Math.round(intensity * 255);
|
||||
|
||||
if (vibeValue > 255) {
|
||||
vibeValue = 255;
|
||||
}
|
||||
if (vibeValue < 1) {
|
||||
stop(device_id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
|
||||
// something went horribly wrong with the Android 8.0 APIs.
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLHapticHandler {
|
||||
|
||||
static class SDLHaptic {
|
||||
public int device_id;
|
||||
public String name;
|
||||
public Vibrator vib;
|
||||
}
|
||||
|
||||
private final ArrayList<SDLHaptic> mHaptics;
|
||||
|
||||
public SDLHapticHandler() {
|
||||
mHaptics = new ArrayList<SDLHaptic>();
|
||||
}
|
||||
|
||||
public void run(int device_id, float intensity, int length) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.vibrate(length);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(int device_id) {
|
||||
SDLHaptic haptic = getHaptic(device_id);
|
||||
if (haptic != null) {
|
||||
haptic.vib.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void pollHapticDevices() {
|
||||
|
||||
final int deviceId_VIBRATOR_SERVICE = 999999;
|
||||
boolean hasVibratorService = false;
|
||||
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
// It helps processing the device ids in reverse order
|
||||
// For example, in the case of the XBox 360 wireless dongle,
|
||||
// so the first controller seen by SDL matches what the receiver
|
||||
// considers to be the first controller
|
||||
|
||||
for (int i = deviceIds.length - 1; i > -1; i--) {
|
||||
SDLHaptic haptic = getHaptic(deviceIds[i]);
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check VIBRATOR_SERVICE */
|
||||
Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vib != null) {
|
||||
hasVibratorService = vib.hasVibrator();
|
||||
|
||||
if (hasVibratorService) {
|
||||
SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
|
||||
if (haptic == null) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceId_VIBRATOR_SERVICE;
|
||||
haptic.name = "VIBRATOR_SERVICE";
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check removed devices */
|
||||
ArrayList<Integer> removedDevices = null;
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
int device_id = haptic.device_id;
|
||||
int i;
|
||||
for (i = 0; i < deviceIds.length; i++) {
|
||||
if (device_id == deviceIds[i]) break;
|
||||
}
|
||||
|
||||
if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
|
||||
if (i == deviceIds.length) {
|
||||
if (removedDevices == null) {
|
||||
removedDevices = new ArrayList<Integer>();
|
||||
}
|
||||
removedDevices.add(device_id);
|
||||
}
|
||||
} // else: don't remove the vibrator if it is still present
|
||||
}
|
||||
|
||||
if (removedDevices != null) {
|
||||
for (int device_id : removedDevices) {
|
||||
SDLControllerManager.nativeRemoveHaptic(device_id);
|
||||
for (int i = 0; i < mHaptics.size(); i++) {
|
||||
if (mHaptics.get(i).device_id == device_id) {
|
||||
mHaptics.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected SDLHaptic getHaptic(int device_id) {
|
||||
for (SDLHaptic haptic : mHaptics) {
|
||||
if (haptic.device_id == device_id) {
|
||||
return haptic;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsRelativeMouse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean inRelativeMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public float getEventX(MotionEvent event) {
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
public float getEventY(MotionEvent event) {
|
||||
return event.getY(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
|
||||
// Handle relative mouse mode
|
||||
if (mRelativeModeEnabled) {
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE) {
|
||||
int action = event.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_HOVER_MOVE) {
|
||||
float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event was not managed, call SDLGenericMotionListener_API12 method
|
||||
return super.onGenericMotion(v, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
|
||||
} else {
|
||||
return event.getX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
if (mRelativeModeEnabled) {
|
||||
return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
|
||||
} else {
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
// Generic Motion (mouse hover, joystick...) events go here
|
||||
private boolean mRelativeModeEnabled;
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotion(View v, MotionEvent event) {
|
||||
float x, y;
|
||||
int action;
|
||||
|
||||
switch ( event.getSource() ) {
|
||||
case InputDevice.SOURCE_JOYSTICK:
|
||||
return SDLControllerManager.handleJoystickMotionEvent(event);
|
||||
|
||||
case InputDevice.SOURCE_MOUSE:
|
||||
// DeX desktop mouse cursor is a separate non-standard input type.
|
||||
case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputDevice.SOURCE_MOUSE_RELATIVE:
|
||||
action = event.getActionMasked();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Event was not managed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inRelativeMode() {
|
||||
return mRelativeModeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
SDLActivity.getContentView().releasePointerCapture();
|
||||
}
|
||||
mRelativeModeEnabled = enabled;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reclaimRelativeMouseModeIfNeeded()
|
||||
{
|
||||
if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventX(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getX(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEventY(MotionEvent event) {
|
||||
// Relative mouse in capture mode will only have relative for X/Y
|
||||
return event.getY(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
package org.libsdl.app;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
/**
|
||||
SDLSurface. This is what we draw on, so we need to know when it's created
|
||||
in order to do anything useful.
|
||||
|
||||
Because of this, that's where we set up the SDL thread
|
||||
*/
|
||||
public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnKeyListener, View.OnTouchListener, SensorEventListener {
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
|
||||
// Keep track of the surface size to normalize touch events
|
||||
protected float mWidth, mHeight;
|
||||
|
||||
// Is SurfaceView ready for rendering
|
||||
public boolean mIsSurfaceReady;
|
||||
|
||||
// Startup
|
||||
public SDLSurface(Context context) {
|
||||
super(context);
|
||||
getHolder().addCallback(this);
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
|
||||
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
|
||||
setOnGenericMotionListener(SDLActivity.getMotionListener());
|
||||
|
||||
// Some arbitrary defaults to avoid a potential division by zero
|
||||
mWidth = 1.0f;
|
||||
mHeight = 1.0f;
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
}
|
||||
|
||||
public void handlePause() {
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, false);
|
||||
}
|
||||
|
||||
public void handleResume() {
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
setOnKeyListener(this);
|
||||
setOnTouchListener(this);
|
||||
enableSensor(Sensor.TYPE_ACCELEROMETER, true);
|
||||
}
|
||||
|
||||
public Surface getNativeSurface() {
|
||||
return getHolder().getSurface();
|
||||
}
|
||||
|
||||
// Called when we have a valid drawing surface
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
// Called when we lose the surface
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
mIsSurfaceReady = false;
|
||||
SDLActivity.onNativeSurfaceDestroyed();
|
||||
}
|
||||
|
||||
// Called when the surface is resized
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
int format, int width, int height) {
|
||||
Log.v("SDL", "surfaceChanged()");
|
||||
|
||||
if (SDLActivity.mSingleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
int nDeviceWidth = width;
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
nDeviceHeight = realMetrics.heightPixels;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
synchronized(SDLActivity.getContext()) {
|
||||
// In case we're waiting on a size change after going fullscreen, send a notification.
|
||||
SDLActivity.getContext().notifyAll();
|
||||
}
|
||||
|
||||
Log.v("SDL", "Window size: " + width + "x" + height);
|
||||
Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
|
||||
SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
|
||||
SDLActivity.onNativeResize();
|
||||
|
||||
// Prevent a screen distortion glitch,
|
||||
// for instance when the device is in Landscape and a Portrait App is resumed.
|
||||
boolean skip = false;
|
||||
int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
|
||||
|
||||
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
|
||||
if (mWidth > mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
|
||||
if (mWidth < mHeight) {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Special Patch for Square Resolution: Black Berry Passport
|
||||
if (skip) {
|
||||
double min = Math.min(mWidth, mHeight);
|
||||
double max = Math.max(mWidth, mHeight);
|
||||
|
||||
if (max / min < 1.20) {
|
||||
Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
Log.v("SDL", "Skip .. Surface is not ready.");
|
||||
mIsSurfaceReady = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
|
||||
SDLActivity.onNativeSurfaceChanged();
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
}
|
||||
|
||||
// Key events
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
return SDLActivity.handleKeyEvent(v, keyCode, event, null);
|
||||
}
|
||||
|
||||
// Touch events
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
/* Ref: http://developer.android.com/training/gestures/multi.html */
|
||||
int touchDevId = event.getDeviceId();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
int action = event.getActionMasked();
|
||||
int pointerFingerId;
|
||||
int i = -1;
|
||||
float x,y,p;
|
||||
|
||||
/*
|
||||
* Prevent id to be -1, since it's used in SDL internal for synthetic events
|
||||
* Appears when using Android emulator, eg:
|
||||
* adb shell input mouse tap 100 100
|
||||
* adb shell input touchscreen tap 100 100
|
||||
*/
|
||||
if (touchDevId < 0) {
|
||||
touchDevId -= 1;
|
||||
}
|
||||
|
||||
// 12290 = Samsung DeX mode desktop mouse
|
||||
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
|
||||
// 0x2 = SOURCE_CLASS_POINTER
|
||||
if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
|
||||
int mouseButton = 1;
|
||||
try {
|
||||
Object object = event.getClass().getMethod("getButtonState").invoke(event);
|
||||
if (object != null) {
|
||||
mouseButton = (Integer) object;
|
||||
}
|
||||
} catch(Exception ignored) {
|
||||
}
|
||||
|
||||
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
|
||||
// if we are. We'll leverage our existing mouse motion listener
|
||||
SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
|
||||
x = motionListener.getEventX(event);
|
||||
y = motionListener.getEventY(event);
|
||||
|
||||
SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
|
||||
} else {
|
||||
switch(action) {
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
// Primary pointer up/down, the index is always zero
|
||||
i = 0;
|
||||
/* fallthrough */
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
// Non primary pointer up/down
|
||||
if (i == -1) {
|
||||
i = event.getActionIndex();
|
||||
}
|
||||
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
for (i = 0; i < pointerCount; i++) {
|
||||
pointerFingerId = event.getPointerId(i);
|
||||
x = event.getX(i) / mWidth;
|
||||
y = event.getY(i) / mHeight;
|
||||
p = event.getPressure(i);
|
||||
if (p > 1.0f) {
|
||||
// may be larger than 1.0f on some devices
|
||||
// see the documentation of getPressure(i)
|
||||
p = 1.0f;
|
||||
}
|
||||
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sensor events
|
||||
public void enableSensor(int sensortype, boolean enabled) {
|
||||
// TODO: This uses getDefaultSensor - what if we have >1 accels?
|
||||
if (enabled) {
|
||||
mSensorManager.registerListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype),
|
||||
SensorManager.SENSOR_DELAY_GAME, null);
|
||||
} else {
|
||||
mSensorManager.unregisterListener(this,
|
||||
mSensorManager.getDefaultSensor(sensortype));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
||||
|
||||
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
|
||||
// We thus should check here.
|
||||
int newOrientation;
|
||||
|
||||
float x, y;
|
||||
switch (mDisplay.getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
x = -event.values[1];
|
||||
y = event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = event.values[1];
|
||||
y = -event.values[0];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -event.values[0];
|
||||
y = -event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
x = event.values[0];
|
||||
y = event.values[1];
|
||||
newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newOrientation != SDLActivity.mCurrentOrientation) {
|
||||
SDLActivity.mCurrentOrientation = newOrientation;
|
||||
SDLActivity.onNativeOrientationChanged(newOrientation);
|
||||
}
|
||||
|
||||
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
|
||||
y / SensorManager.GRAVITY_EARTH,
|
||||
event.values[2] / SensorManager.GRAVITY_EARTH);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Captured pointer events for API 26.
|
||||
public boolean onCapturedPointerEvent(MotionEvent event)
|
||||
{
|
||||
int action = event.getActionMasked();
|
||||
|
||||
float x, y;
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
|
||||
y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, false);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
SDLActivity.onNativeMouse(0, action, x, y, true);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_BUTTON_PRESS:
|
||||
case MotionEvent.ACTION_BUTTON_RELEASE:
|
||||
|
||||
// Change our action value to what SDL's code expects.
|
||||
if (action == MotionEvent.ACTION_BUTTON_PRESS) {
|
||||
action = MotionEvent.ACTION_DOWN;
|
||||
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
|
||||
action = MotionEvent.ACTION_UP;
|
||||
}
|
||||
|
||||
x = event.getX(0);
|
||||
y = event.getY(0);
|
||||
int button = event.getButtonState();
|
||||
|
||||
SDLActivity.onNativeMouse(button, action, x, y, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -490,6 +490,9 @@ end
|
|||
|
||||
|
||||
function table.insert_all(t, other)
|
||||
if table.move then -- LuaJIT
|
||||
return table.move(other, 1, #other, #t + 1, t)
|
||||
end
|
||||
for i=1, #other do
|
||||
t[#t + 1] = other[i]
|
||||
end
|
||||
|
|
|
@ -39,6 +39,7 @@ core.features = {
|
|||
dynamic_add_media_filepath = true,
|
||||
lsystem_decoration_type = true,
|
||||
item_meta_range = true,
|
||||
node_interaction_actor = true,
|
||||
}
|
||||
|
||||
function core.has_feature(arg)
|
||||
|
|
|
@ -26,7 +26,15 @@ do
|
|||
core.print = nil -- don't pollute our namespace
|
||||
end
|
||||
end
|
||||
math.randomseed(os.time())
|
||||
|
||||
do
|
||||
-- Note that PUC Lua just calls srand() which is already initialized by C++,
|
||||
-- but we don't want to rely on this implementation detail.
|
||||
local seed = 1048576 * (os.time() % 1048576)
|
||||
seed = seed + core.get_us_time() % 1048576
|
||||
math.randomseed(seed)
|
||||
end
|
||||
|
||||
minetest = core
|
||||
|
||||
-- Load other files
|
||||
|
|
|
@ -18,6 +18,26 @@
|
|||
-- Global menu data
|
||||
menudata = {}
|
||||
|
||||
-- located in user cache path, for remembering this like e.g. last update check
|
||||
cache_settings = Settings(core.get_cache_path() .. DIR_DELIM .. "common.conf")
|
||||
|
||||
--- Checks if the given key contains a timestamp less than a certain age.
|
||||
--- Pair this with a call to `cache_settings:set(key, tostring(os.time()))`
|
||||
--- after successfully refreshing the cache.
|
||||
--- @param key Name of entry in cache_settings
|
||||
--- @param max_age Age to check against, in seconds
|
||||
--- @return true if the max age is not reached
|
||||
function check_cache_age(key, max_age)
|
||||
local time_now = os.time()
|
||||
local time_checked = tonumber(cache_settings:get(key)) or 0
|
||||
return time_now - time_checked < max_age
|
||||
end
|
||||
|
||||
function core.on_before_close()
|
||||
-- called before the menu is closed, either exit or to join a game
|
||||
cache_settings:write()
|
||||
end
|
||||
|
||||
-- Local cached values
|
||||
local min_supp_proto, max_supp_proto
|
||||
|
||||
|
@ -27,6 +47,16 @@ function common_update_cached_supp_proto()
|
|||
end
|
||||
common_update_cached_supp_proto()
|
||||
|
||||
-- Other global functions
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
||||
|
||||
function os.tmpname()
|
||||
error('do not use') -- instead: core.get_temp_path()
|
||||
end
|
||||
|
||||
-- Menu helper functions
|
||||
|
||||
local function render_client_count(n)
|
||||
|
@ -140,11 +170,6 @@ function render_serverlist_row(spec)
|
|||
|
||||
return table.concat(details, ",")
|
||||
end
|
||||
---------------------------------------------------------------------------------
|
||||
os.tmpname = function()
|
||||
error('do not use') -- instead use core.get_temp_path()
|
||||
end
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function menu_render_worldlist()
|
||||
local retval = {}
|
||||
|
|
|
@ -98,15 +98,12 @@ local function download_and_extract(param)
|
|||
|
||||
local tempfolder = core.get_temp_path()
|
||||
if tempfolder ~= "" then
|
||||
tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
|
||||
if not core.extract_zip(filename, tempfolder) then
|
||||
tempfolder = nil
|
||||
end
|
||||
else
|
||||
tempfolder = nil
|
||||
end
|
||||
os.remove(filename)
|
||||
if not tempfolder then
|
||||
if not tempfolder or tempfolder == "" then
|
||||
return {
|
||||
msg = fgettext_ne("Failed to extract \"$1\" (unsupported file type or broken archive)", package.title),
|
||||
}
|
||||
|
|
|
@ -26,13 +26,23 @@ if not core.get_http_api then
|
|||
end
|
||||
|
||||
|
||||
assert(core.create_dir(core.get_cache_path() .. DIR_DELIM .. "cdb"))
|
||||
local cache_file_path = core.get_cache_path() .. DIR_DELIM .. "cdb" .. DIR_DELIM .. "updates.json"
|
||||
local has_fetched = false
|
||||
local latest_releases
|
||||
do
|
||||
local tmp = core.get_once("cdb_latest_releases")
|
||||
if tmp then
|
||||
latest_releases = core.deserialize(tmp, true)
|
||||
has_fetched = latest_releases ~= nil
|
||||
if check_cache_age("cdb_updates_last_checked", 3 * 3600) then
|
||||
local f = io.open(cache_file_path, "r")
|
||||
local data = ""
|
||||
if f then
|
||||
data = f:read("*a")
|
||||
f:close()
|
||||
end
|
||||
data = data ~= "" and core.parse_json(data) or nil
|
||||
if type(data) == "table" then
|
||||
latest_releases = data
|
||||
has_fetched = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -97,7 +107,8 @@ local function fetch()
|
|||
return
|
||||
end
|
||||
latest_releases = lowercase_keys(releases)
|
||||
core.set_once("cdb_latest_releases", core.serialize(latest_releases))
|
||||
core.safe_file_write(cache_file_path, core.write_json(latest_releases))
|
||||
cache_settings:set("cdb_updates_last_checked", tostring(os.time()))
|
||||
|
||||
if update_detector.get_count() > 0 then
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
|
|
|
@ -15,15 +15,30 @@
|
|||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
---- IMPORTANT ----
|
||||
-- This whole file can be removed after a while.
|
||||
-- It was only directly useful for upgrades from 5.7.0 to 5.8.0, but
|
||||
-- maybe some odd fellow directly upgrades from 5.6.1 to 5.9.0 in the future...
|
||||
-- see <https://github.com/minetest/minetest/pull/13850> in case it's not obvious
|
||||
---- ----
|
||||
|
||||
local SETTING_NAME = "no_mtg_notification"
|
||||
|
||||
function check_reinstall_mtg()
|
||||
if core.settings:get_bool("no_mtg_notification") then
|
||||
-- used to be in minetest.conf
|
||||
if core.settings:get_bool(SETTING_NAME) then
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
core.settings:remove(SETTING_NAME)
|
||||
end
|
||||
|
||||
if cache_settings:get_bool(SETTING_NAME) then
|
||||
return
|
||||
end
|
||||
|
||||
local games = core.get_games()
|
||||
for _, game in ipairs(games) do
|
||||
if game.id == "minetest" then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
@ -37,7 +52,7 @@ function check_reinstall_mtg()
|
|||
end
|
||||
end
|
||||
if not mtg_world_found then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
cache_settings:set_bool(SETTING_NAME, true)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -87,7 +102,7 @@ local function buttonhandler(this, fields)
|
|||
end
|
||||
|
||||
if fields.dismiss then
|
||||
core.settings:set_bool("no_mtg_notification", true)
|
||||
cache_settings:set_bool("no_mtg_notification", true)
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
|
|
@ -51,12 +51,13 @@ end
|
|||
local function version_info_buttonhandler(this, fields)
|
||||
if fields.version_check_remind then
|
||||
-- Erase last known, user will be reminded again at next check
|
||||
core.settings:set("update_last_known", "")
|
||||
cache_settings:set("update_last_known", "")
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
if fields.version_check_never then
|
||||
core.settings:set("update_last_checked", "disabled")
|
||||
-- clear checked URL
|
||||
core.settings:set("update_information_url", "")
|
||||
this:delete()
|
||||
return true
|
||||
end
|
||||
|
@ -116,7 +117,7 @@ local function on_version_info_received(json)
|
|||
return
|
||||
end
|
||||
|
||||
local known_update = tonumber(core.settings:get("update_last_known")) or 0
|
||||
local known_update = tonumber(cache_settings:get("update_last_known")) or 0
|
||||
|
||||
-- Format: MMNNPPP (Major, Minor, Patch)
|
||||
local new_number = type(json.latest) == "table" and json.latest.version_code
|
||||
|
@ -135,7 +136,7 @@ local function on_version_info_received(json)
|
|||
return
|
||||
end
|
||||
|
||||
core.settings:set("update_last_known", tostring(new_number))
|
||||
cache_settings:set("update_last_known", tostring(new_number))
|
||||
|
||||
-- Show version info dialog (once)
|
||||
maintab:hide()
|
||||
|
@ -149,20 +150,20 @@ end
|
|||
|
||||
function check_new_version()
|
||||
local url = core.settings:get("update_information_url")
|
||||
if core.settings:get("update_last_checked") == "disabled" or
|
||||
url == "" then
|
||||
if url == "" then
|
||||
-- Never show any updates
|
||||
return
|
||||
end
|
||||
|
||||
local time_now = os.time()
|
||||
local time_checked = tonumber(core.settings:get("update_last_checked")) or 0
|
||||
if time_now - time_checked < 2 * 24 * 3600 then
|
||||
-- Check interval of 2 entire days
|
||||
-- every 2 days
|
||||
if check_cache_age("update_last_checked", 2 * 24 * 3600) then
|
||||
return
|
||||
end
|
||||
cache_settings:set("update_last_checked", tostring(os.time()))
|
||||
|
||||
core.settings:set("update_last_checked", tostring(time_now))
|
||||
-- Clean old leftovers (this can be removed after 5.9.0 or so)
|
||||
core.settings:remove("update_last_checked")
|
||||
core.settings:remove("update_last_known")
|
||||
|
||||
core.handle_async(function(params)
|
||||
local http = core.get_http_api()
|
||||
|
|
|
@ -28,8 +28,6 @@ local basepath = core.get_builtin_path()
|
|||
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
|
||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||
|
||||
dofile(menupath .. DIR_DELIM .. "misc.lua")
|
||||
|
||||
dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
|
||||
dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua")
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
-- old non-method sound function
|
||||
|
||||
function core.sound_stop(handle, ...)
|
||||
return handle:stop(...)
|
||||
end
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
serverlistmgr = {
|
||||
-- continent code we detected for ourselves
|
||||
my_continent = core.get_once("continent"),
|
||||
my_continent = nil,
|
||||
|
||||
-- list of locally favorites servers
|
||||
favorites = nil,
|
||||
|
@ -26,6 +26,15 @@ serverlistmgr = {
|
|||
servers = nil,
|
||||
}
|
||||
|
||||
do
|
||||
if check_cache_age("geoip_last_checked", 3600) then
|
||||
local tmp = cache_settings:get("geoip") or ""
|
||||
if tmp:match("^[A-Z][A-Z]$") then
|
||||
serverlistmgr.my_continent = tmp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Efficient data structure for normalizing arbitrary scores attached to objects
|
||||
-- e.g. {{"a", 3.14}, {"b", 3.14}, {"c", 20}, {"d", 0}}
|
||||
|
@ -71,7 +80,8 @@ local WEIGHT_SORT = 2
|
|||
-- how much the estimated latency contributes to the final ranking
|
||||
local WEIGHT_LATENCY = 1
|
||||
|
||||
local function order_server_list(list)
|
||||
--- @param list of servers, will be modified.
|
||||
local function order_server_list_internal(list)
|
||||
-- calculate the scores
|
||||
local s1 = Normalizer:new()
|
||||
local s2 = Normalizer:new()
|
||||
|
@ -90,28 +100,58 @@ local function order_server_list(list)
|
|||
s1 = s1:calc()
|
||||
s2 = s2:calc()
|
||||
|
||||
-- make a shallow copy and pre-calculate ordering
|
||||
local res, order = {}, {}
|
||||
for i = 1, #list do
|
||||
local fav = list[i]
|
||||
res[i] = fav
|
||||
|
||||
local n = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
|
||||
order[fav] = n
|
||||
-- pre-calculate ordering
|
||||
local order = {}
|
||||
for _, fav in ipairs(list) do
|
||||
order[fav] = s1[fav] * WEIGHT_SORT + s2[fav] * WEIGHT_LATENCY
|
||||
end
|
||||
|
||||
-- now sort the list
|
||||
table.sort(res, function(fav1, fav2)
|
||||
table.sort(list, function(fav1, fav2)
|
||||
return order[fav1] > order[fav2]
|
||||
end)
|
||||
end
|
||||
|
||||
return res
|
||||
local function order_server_list(list)
|
||||
-- split the list into two parts and sort them separately, to keep empty
|
||||
-- servers at the bottom.
|
||||
local nonempty, empty = {}, {}
|
||||
|
||||
for _, fav in ipairs(list) do
|
||||
if (fav.clients or 0) > 0 then
|
||||
table.insert(nonempty, fav)
|
||||
else
|
||||
table.insert(empty, fav)
|
||||
end
|
||||
end
|
||||
|
||||
order_server_list_internal(nonempty)
|
||||
order_server_list_internal(empty)
|
||||
|
||||
table.insert_all(nonempty, empty)
|
||||
return nonempty
|
||||
end
|
||||
|
||||
local public_downloading = false
|
||||
local geoip_downloading = false
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
local function fetch_geoip()
|
||||
local http = core.get_http_api()
|
||||
local url = core.settings:get("serverlist_url") .. "/geoip"
|
||||
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
return
|
||||
end
|
||||
|
||||
local retval = core.parse_json(response.data)
|
||||
if type(retval) ~= "table" then
|
||||
return
|
||||
end
|
||||
return type(retval.continent) == "string" and retval.continent
|
||||
end
|
||||
|
||||
function serverlistmgr.sync()
|
||||
if not serverlistmgr.servers then
|
||||
serverlistmgr.servers = {{
|
||||
|
@ -129,37 +169,23 @@ function serverlistmgr.sync()
|
|||
return
|
||||
end
|
||||
|
||||
-- only fetched once per MT instance
|
||||
if not serverlistmgr.my_continent and not geoip_downloading then
|
||||
geoip_downloading = true
|
||||
core.handle_async(
|
||||
function(param)
|
||||
local http = core.get_http_api()
|
||||
local url = core.settings:get("serverlist_url") .. "/geoip"
|
||||
|
||||
local response = http.fetch_sync({ url = url })
|
||||
if not response.succeeded then
|
||||
return
|
||||
end
|
||||
|
||||
local retval = core.parse_json(response.data)
|
||||
return retval and type(retval.continent) == "string" and retval.continent
|
||||
end,
|
||||
nil,
|
||||
function(result)
|
||||
geoip_downloading = false
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
serverlistmgr.my_continent = result
|
||||
core.set_once("continent", result)
|
||||
-- reorder list if we already have it
|
||||
if serverlistmgr.servers then
|
||||
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
|
||||
core.event_handler("Refresh")
|
||||
end
|
||||
core.handle_async(fetch_geoip, nil, function(result)
|
||||
geoip_downloading = false
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
)
|
||||
serverlistmgr.my_continent = result
|
||||
cache_settings:set("geoip", result)
|
||||
cache_settings:set("geoip_last_checked", tostring(os.time()))
|
||||
|
||||
-- re-sort list if applicable
|
||||
if serverlistmgr.servers then
|
||||
serverlistmgr.servers = order_server_list(serverlistmgr.servers)
|
||||
core.event_handler("Refresh")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if public_downloading then
|
||||
|
@ -167,6 +193,7 @@ function serverlistmgr.sync()
|
|||
end
|
||||
public_downloading = true
|
||||
|
||||
-- note: this isn't cached because it's way too dynamic
|
||||
core.handle_async(
|
||||
function(param)
|
||||
local http = core.get_http_api()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
_G.core = {get_once = function(_) end}
|
||||
_G.core = {}
|
||||
_G.unpack = table.unpack
|
||||
_G.check_cache_age = function() return false end
|
||||
_G.serverlistmgr = {}
|
||||
|
||||
dofile("builtin/common/vector.lua")
|
||||
|
|
|
@ -114,7 +114,11 @@ always_fly_fast (Always fly fast) bool true
|
|||
# the place button.
|
||||
#
|
||||
# Requires: keyboard_mouse
|
||||
repeat_place_time (Place repetition interval) float 0.25 0.16 2
|
||||
repeat_place_time (Place repetition interval) float 0.25 0.15 2.0
|
||||
|
||||
# The minimum time in seconds it takes between digging nodes when holding
|
||||
# the dig button.
|
||||
repeat_dig_time (Dig repetition interval) float 0.15 0.15 2.0
|
||||
|
||||
# Automatically jump up single-node obstacles.
|
||||
autojump (Automatic jumping) bool false
|
||||
|
@ -149,20 +153,23 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
|
|||
[*Touchscreen]
|
||||
|
||||
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
|
||||
#
|
||||
# Requires: !android
|
||||
enable_touch (Enable touchscreen) bool true
|
||||
|
||||
# The length in pixels it takes for touchscreen interaction to start.
|
||||
#
|
||||
# Requires: touchscreen_gui
|
||||
touchscreen_threshold (Touchscreen threshold) int 20 0 100
|
||||
|
||||
# Touchscreen sensitivity multiplier.
|
||||
#
|
||||
# Requires: touchscreen_gui
|
||||
touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0
|
||||
|
||||
# The length in pixels after which a touch interaction is considered movement.
|
||||
#
|
||||
# Requires: touchscreen_gui
|
||||
touchscreen_threshold (Movement threshold) int 20 0 100
|
||||
|
||||
# The delay in milliseconds after which a touch interaction is considered a long tap.
|
||||
#
|
||||
# Requires: touchscreen_gui
|
||||
touch_long_tap_delay (Threshold for long taps) int 400 100 1000
|
||||
|
||||
# Use crosshair to select object instead of whole screen.
|
||||
# If enabled, a crosshair will be shown and will be used for selecting object.
|
||||
#
|
||||
|
@ -181,6 +188,19 @@ fixed_virtual_joystick (Fixed virtual joystick) bool false
|
|||
# Requires: touchscreen_gui
|
||||
virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false
|
||||
|
||||
# The gesture for for punching players/entities.
|
||||
# This can be overridden by games and mods.
|
||||
#
|
||||
# * short_tap
|
||||
# Easy to use and well-known from other games that shall not be named.
|
||||
#
|
||||
# * long_tap
|
||||
# Known from the classic Minetest mobile controls.
|
||||
# Combat is more or less impossible.
|
||||
#
|
||||
# Requires: touchscreen_gui
|
||||
touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap
|
||||
|
||||
|
||||
[Graphics and Audio]
|
||||
|
||||
|
@ -822,6 +842,7 @@ serverlist_url (Serverlist URL) string servers.minetest.net
|
|||
enable_split_login_register (Enable split login/register) bool true
|
||||
|
||||
# URL to JSON file which provides information about the newest Minetest release
|
||||
# If this is empty the engine will never check for updates.
|
||||
update_information_url (Update information URL) string https://www.minetest.net/release_info.json
|
||||
|
||||
[*Server]
|
||||
|
@ -2337,20 +2358,6 @@ show_advanced (Show advanced settings) bool false
|
|||
# Changing this setting requires a restart.
|
||||
enable_sound (Sound) bool true
|
||||
|
||||
# Unix timestamp (integer) of when the client last checked for an update
|
||||
# Set this value to "disabled" to never check for updates.
|
||||
update_last_checked (Last update check) string
|
||||
|
||||
# Version number which was last seen during an update check.
|
||||
#
|
||||
# Representation: MMMIIIPPP, where M=Major, I=Minor, P=Patch
|
||||
# Ex: 5.5.0 is 005005000
|
||||
update_last_known (Last known version update) int 0
|
||||
|
||||
# If this is set to true, the user will never (again) be shown the
|
||||
# "reinstall Minetest Game" notification.
|
||||
no_mtg_notification (Don't show "reinstall Minetest Game" notification) bool false
|
||||
|
||||
# Key for moving the player forward.
|
||||
keymap_forward (Forward key) key KEY_KEY_W
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../irr/media/Shaders
|
|
@ -1,7 +1,7 @@
|
|||
uniform vec4 fogColor;
|
||||
uniform lowp vec4 fogColor;
|
||||
uniform float fogDistance;
|
||||
uniform float fogShadingParameter;
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
|
||||
varying lowp vec4 varColor;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
uniform vec4 emissiveColor;
|
||||
uniform lowp vec4 emissiveColor;
|
||||
|
||||
varying lowp vec4 varColor;
|
||||
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
|
|
|
@ -9,13 +9,12 @@ uniform sampler2D baseTexture;
|
|||
uniform vec2 texelSize0;
|
||||
|
||||
uniform vec3 dayLight;
|
||||
uniform vec4 fogColor;
|
||||
uniform lowp vec4 fogColor;
|
||||
uniform float fogDistance;
|
||||
uniform float fogShadingParameter;
|
||||
uniform vec3 eyePosition;
|
||||
|
||||
// The cameraOffset is the current center of the visible world.
|
||||
uniform vec3 cameraOffset;
|
||||
uniform highp vec3 cameraOffset;
|
||||
uniform float animationTimer;
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
// shadow texture
|
||||
|
@ -53,11 +52,8 @@ varying mediump vec2 varTexCoord;
|
|||
#else
|
||||
centroid varying vec2 varTexCoord;
|
||||
#endif
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
varying vec3 tsEyeVec;
|
||||
varying vec3 lightVec;
|
||||
varying vec3 tsLightVec;
|
||||
|
||||
varying vec3 viewVec;
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
uniform mat4 mWorld;
|
||||
// Color of the light emitted by the sun.
|
||||
uniform vec3 dayLight;
|
||||
uniform vec3 eyePosition;
|
||||
|
||||
// The cameraOffset is the current center of the visible world.
|
||||
uniform vec3 cameraOffset;
|
||||
uniform highp vec3 cameraOffset;
|
||||
uniform float animationTimer;
|
||||
|
||||
varying vec3 vNormal;
|
||||
|
@ -45,7 +44,7 @@ centroid varying vec2 varTexCoord;
|
|||
varying float area_enable_parallax;
|
||||
|
||||
varying vec3 viewVec;
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
// Color of the light emitted by the light sources.
|
||||
uniform vec3 artificialLight;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
uniform sampler2D baseTexture;
|
||||
|
||||
uniform vec3 dayLight;
|
||||
uniform vec4 fogColor;
|
||||
uniform lowp vec4 fogColor;
|
||||
uniform float fogDistance;
|
||||
uniform float fogShadingParameter;
|
||||
uniform vec3 eyePosition;
|
||||
|
||||
// The cameraOffset is the current center of the visible world.
|
||||
uniform vec3 cameraOffset;
|
||||
uniform highp vec3 cameraOffset;
|
||||
uniform float animationTimer;
|
||||
#ifdef ENABLE_DYNAMIC_SHADOWS
|
||||
// shadow texture
|
||||
|
@ -45,7 +44,7 @@ varying mediump vec2 varTexCoord;
|
|||
#else
|
||||
centroid varying vec2 varTexCoord;
|
||||
#endif
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
|
||||
varying float vIDiff;
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
uniform mat4 mWorld;
|
||||
uniform vec3 dayLight;
|
||||
uniform vec3 eyePosition;
|
||||
uniform float animationTimer;
|
||||
uniform vec4 emissiveColor;
|
||||
uniform vec3 cameraOffset;
|
||||
|
||||
uniform lowp vec4 emissiveColor;
|
||||
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vPosition;
|
||||
|
@ -33,7 +30,7 @@ centroid varying vec2 varTexCoord;
|
|||
varying float perspective_factor;
|
||||
#endif
|
||||
|
||||
varying vec3 eyeVec;
|
||||
varying highp vec3 eyeVec;
|
||||
varying float nightRatio;
|
||||
// Color of the light emitted by the light sources.
|
||||
uniform vec3 artificialLight;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
uniform vec4 emissiveColor;
|
||||
uniform lowp vec4 emissiveColor;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
|
|
|
@ -25,3 +25,5 @@ set(VORBIS_LIBRARY ${DEPS}/Vorbis/libvorbis.a)
|
|||
set(VORBISFILE_LIBRARY ${DEPS}/Vorbis/libvorbisfile.a)
|
||||
set(ZSTD_INCLUDE_DIR ${DEPS}/Zstd/include)
|
||||
set(ZSTD_LIBRARY ${DEPS}/Zstd/libzstd.a)
|
||||
set(SDL2_INCLUDE_DIRS ${DEPS}/SDL2/include/SDL2)
|
||||
set(SDL2_LIBRARIES ${DEPS}/SDL2/libSDL2.a)
|
||||
|
|
|
@ -659,8 +659,9 @@ The mask is applied using binary AND.
|
|||
|
||||
#### `[sheet:<w>x<h>:<x>,<y>`
|
||||
|
||||
Retrieves a tile at position x,y from the base image
|
||||
which it assumes to be a tilesheet with dimensions w,h.
|
||||
Retrieves a tile at position x, y (in tiles, 0-indexed)
|
||||
from the base image, which it assumes to be a tilesheet
|
||||
with dimensions w, h (in tiles).
|
||||
|
||||
#### `[colorize:<color>:<ratio>`
|
||||
|
||||
|
@ -5431,6 +5432,9 @@ Utilities
|
|||
lsystem_decoration_type = true,
|
||||
-- Overrideable pointing range using the itemstack meta key `"range"` (5.9.0)
|
||||
item_meta_range = true,
|
||||
-- Allow passing an optional "actor" ObjectRef to the following functions:
|
||||
-- minetest.place_node, minetest.dig_node, minetest.punch_node (5.9.0)
|
||||
node_interaction_actor = true,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -6061,13 +6065,16 @@ Environment access
|
|||
* Returns a number between `0` and `15`
|
||||
* Currently it's the same as `math.floor(param1 / 16)`, except that it
|
||||
ensures compatibility.
|
||||
* `minetest.place_node(pos, node)`
|
||||
* `minetest.place_node(pos, node[, placer])`
|
||||
* Place node with the same effects that a player would cause
|
||||
* `minetest.dig_node(pos)`
|
||||
* `placer`: The ObjectRef that places the node (optional)
|
||||
* `minetest.dig_node(pos[, digger])`
|
||||
* Dig node with the same effects that a player would cause
|
||||
* `digger`: The ObjectRef that digs the node (optional)
|
||||
* Returns `true` if successful, `false` on failure (e.g. protected location)
|
||||
* `minetest.punch_node(pos)`
|
||||
* `minetest.punch_node(pos[, puncher])`
|
||||
* Punch node with the same effects that a player would cause
|
||||
* `puncher`: The ObjectRef that punches the node (optional)
|
||||
* `minetest.spawn_falling_node(pos)`
|
||||
* Change node into falling node
|
||||
* Returns `true` and the ObjectRef of the spawned entity if successful, `false` on failure
|
||||
|
@ -7313,8 +7320,41 @@ Global tables
|
|||
All callbacks registered with [Global callback registration functions] are added
|
||||
to corresponding `minetest.registered_*` tables.
|
||||
|
||||
For historical reasons, the use of an -s suffix in these names is inconsistent.
|
||||
|
||||
|
||||
* `minetest.registered_on_chat_messages`
|
||||
* `minetest.registered_on_chatcommands`
|
||||
* `minetest.registered_globalsteps`
|
||||
* `minetest.registered_on_punchnodes`
|
||||
* `minetest.registered_on_placenodes`
|
||||
* `minetest.registered_on_dignodes`
|
||||
* `minetest.registered_on_generateds`
|
||||
* `minetest.registered_on_newplayers`
|
||||
* `minetest.registered_on_dieplayers`
|
||||
* `minetest.registered_on_respawnplayers`
|
||||
* `minetest.registered_on_prejoinplayers`
|
||||
* `minetest.registered_on_joinplayers`
|
||||
* `minetest.registered_on_leaveplayers`
|
||||
* `minetest.registered_on_player_receive_fields`
|
||||
* `minetest.registered_on_cheats`
|
||||
* `minetest.registered_on_crafts`
|
||||
* `minetest.registered_craft_predicts`
|
||||
* `minetest.registered_on_item_eats`
|
||||
* `minetest.registered_on_item_pickups`
|
||||
* `minetest.registered_on_punchplayers`
|
||||
* `minetest.registered_on_authplayers`
|
||||
* `minetest.registered_on_player_inventory_actions`
|
||||
* `minetest.registered_allow_player_inventory_actions`
|
||||
* `minetest.registered_on_rightclickplayers`
|
||||
* `minetest.registered_on_mods_loaded`
|
||||
* `minetest.registered_on_shutdown`
|
||||
* `minetest.registered_on_protection_violation`
|
||||
* `minetest.registered_on_priv_grant`
|
||||
* `minetest.registered_on_priv_revoke`
|
||||
* `minetest.registered_can_bypass_userlimit`
|
||||
* `minetest.registered_on_modchannel_message`
|
||||
* `minetest.registered_on_liquid_transformed`
|
||||
* `minetest.registered_on_mapblocks_changed`
|
||||
|
||||
Class reference
|
||||
===============
|
||||
|
@ -7944,8 +7984,12 @@ child will follow movement and rotation of that bone.
|
|||
* Fourth column: subject looking to the right
|
||||
* Fifth column: subject viewed from above
|
||||
* Sixth column: subject viewed from below
|
||||
* `get_entity_name()` (**Deprecated**: Will be removed in a future version, use the field `self.name` instead)
|
||||
* `get_luaentity()`: returns the object's associated luaentity table
|
||||
* `get_luaentity()`:
|
||||
* Returns the object's associated luaentity table, if there is one
|
||||
* Otherwise returns `nil` (e.g. for players)
|
||||
* `get_entity_name()`:
|
||||
* **Deprecated**: Will be removed in a future version,
|
||||
use `:get_luaentity().name` instead.
|
||||
|
||||
#### Player only (no-op for other objects)
|
||||
|
||||
|
@ -8666,7 +8710,8 @@ Player properties need to be saved manually.
|
|||
-- "mesh" uses the defined mesh model.
|
||||
-- "wielditem" is used for dropped items.
|
||||
-- (see builtin/game/item_entity.lua).
|
||||
-- For this use 'wield_item = itemname' (Deprecated: 'textures = {itemname}').
|
||||
-- For this use 'wield_item = itemname'.
|
||||
-- Setting 'textures = {itemname}' has the same effect, but is deprecated.
|
||||
-- If the item has a 'wield_image' the object will be an extrusion of
|
||||
-- that, otherwise:
|
||||
-- If 'itemname' is a cubic node or nodebox the object will appear
|
||||
|
@ -8693,8 +8738,8 @@ Player properties need to be saved manually.
|
|||
-- "cube" uses 6 textures just like a node, but all 6 must be defined.
|
||||
-- "sprite" uses 1 texture.
|
||||
-- "upright_sprite" uses 2 textures: {front, back}.
|
||||
-- "wielditem" expects 'textures = {itemname}' (see 'visual' above).
|
||||
-- "mesh" requires one texture for each mesh buffer/material (in order)
|
||||
-- Deprecated usage of "wielditem" expects 'textures = {itemname}' (see 'visual' above).
|
||||
|
||||
colors = {},
|
||||
-- Number of required colors depends on visual
|
||||
|
@ -9097,19 +9142,20 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
|
|||
-- Otherwise should be name of node which the client immediately places
|
||||
-- upon digging. Server will always update with actual result shortly.
|
||||
|
||||
touch_interaction = {
|
||||
-- Only affects touchscreen clients.
|
||||
-- Defines the meaning of short and long taps with the item in hand.
|
||||
-- The fields in this table have two valid values:
|
||||
-- * "long_dig_short_place" (long tap = dig, short tap = place)
|
||||
-- * "short_dig_long_place" (short tap = dig, long tap = place)
|
||||
-- The field to be used is selected according to the current
|
||||
-- `pointed_thing`.
|
||||
|
||||
pointed_nothing = "long_dig_short_place",
|
||||
pointed_node = "long_dig_short_place",
|
||||
pointed_object = "short_dig_long_place",
|
||||
touch_interaction = <TouchInteractionMode> OR {
|
||||
pointed_nothing = <TouchInteractionMode>,
|
||||
pointed_node = <TouchInteractionMode>,
|
||||
pointed_object = <TouchInteractionMode>,
|
||||
},
|
||||
-- Only affects touchscreen clients.
|
||||
-- Defines the meaning of short and long taps with the item in hand.
|
||||
-- If specified as a table, the field to be used is selected according to
|
||||
-- the current `pointed_thing`.
|
||||
-- There are three possible TouchInteractionMode values:
|
||||
-- * "user" (meaning depends on client-side settings)
|
||||
-- * "long_dig_short_place" (long tap = dig, short tap = place)
|
||||
-- * "short_dig_long_place" (short tap = dig, long tap = place)
|
||||
-- The default value is "user".
|
||||
|
||||
sound = {
|
||||
-- Definition of item sounds to be played at various events.
|
||||
|
|
|
@ -55,10 +55,6 @@ Functions
|
|||
* Android only. Shares file using the share popup
|
||||
* `core.get_version()` (possible in async calls)
|
||||
* returns current core version
|
||||
* `core.set_once(key, value)`:
|
||||
* save a string value that persists even if menu is closed
|
||||
* `core.get_once(key)`:
|
||||
* get a string value saved by above function, or `nil`
|
||||
|
||||
|
||||
|
||||
|
@ -97,8 +93,8 @@ Filesystem
|
|||
registered in the core (possible in async calls)
|
||||
* `core.get_cache_path()` -> path of cache
|
||||
* `core.get_temp_path([param])` (possible in async calls)
|
||||
* `param`=true: returns path to a temporary file
|
||||
otherwise: returns path to the temporary folder
|
||||
* `param`=true: returns path to a newly created temporary file
|
||||
* otherwise: returns path to a newly created temporary folder
|
||||
|
||||
|
||||
HTTP Requests
|
||||
|
|
|
@ -79,9 +79,6 @@ enum EEVENT_TYPE
|
|||
*/
|
||||
EET_USER_EVENT,
|
||||
|
||||
//! Pass on raw events from the OS
|
||||
EET_SYSTEM_EVENT,
|
||||
|
||||
//! Application state events like a resume, pause etc.
|
||||
EET_APPLICATION_EVENT,
|
||||
|
||||
|
@ -187,17 +184,6 @@ enum ETOUCH_INPUT_EVENT
|
|||
ETIE_COUNT
|
||||
};
|
||||
|
||||
enum ESYSTEM_EVENT_TYPE
|
||||
{
|
||||
//! From Android command handler for native activity messages
|
||||
ESET_ANDROID_CMD = 0,
|
||||
|
||||
// TODO: for example ESET_WINDOWS_MESSAGE for win32 message loop events
|
||||
|
||||
//! No real event, but to get number of event types
|
||||
ESET_COUNT
|
||||
};
|
||||
|
||||
//! Enumeration for a commonly used application state events (it's useful mainly for mobile devices)
|
||||
enum EAPPLICATION_EVENT_TYPE
|
||||
{
|
||||
|
@ -528,25 +514,6 @@ struct SEvent
|
|||
size_t UserData2;
|
||||
};
|
||||
|
||||
// Raw events from the OS
|
||||
struct SSystemEvent
|
||||
{
|
||||
//! Android command handler native activity messages.
|
||||
struct SAndroidCmd
|
||||
{
|
||||
//! APP_CMD_ enums defined in android_native_app_glue.h from the Android NDK
|
||||
s32 Cmd;
|
||||
};
|
||||
|
||||
// TOOD: more structs for iphone, Windows, X11, etc.
|
||||
|
||||
ESYSTEM_EVENT_TYPE EventType;
|
||||
union
|
||||
{
|
||||
struct SAndroidCmd AndroidCmd;
|
||||
};
|
||||
};
|
||||
|
||||
// Application state event
|
||||
struct SApplicationEvent
|
||||
{
|
||||
|
@ -567,7 +534,6 @@ struct SEvent
|
|||
struct SJoystickEvent JoystickEvent;
|
||||
struct SLogEvent LogEvent;
|
||||
struct SUserEvent UserEvent;
|
||||
struct SSystemEvent SystemEvent;
|
||||
struct SApplicationEvent ApplicationEvent;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -180,10 +180,7 @@ public:
|
|||
virtual bool isFullscreen() const = 0;
|
||||
|
||||
//! Checks if the window could possibly be visible.
|
||||
//! Currently, this only returns false when the activity is stopped on
|
||||
//! Android. Note that for Android activities, "stopped" means something
|
||||
//! different than you might expect (and also something different than
|
||||
//! "paused"). Read the Android lifecycle documentation.
|
||||
/** If this returns false, you should not do any rendering. */
|
||||
virtual bool isWindowVisible() const { return true; };
|
||||
|
||||
//! Get the current color format of the window
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "CAndroidAssetReader.h"
|
||||
|
||||
#include "CReadFile.h"
|
||||
#include "coreutil.h"
|
||||
#include "CAndroidAssetFileArchive.h"
|
||||
#include "CIrrDeviceAndroid.h"
|
||||
#include "os.h" // for logging (just keep it in even when not needed right now as it's used all the time)
|
||||
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/native_activity.h>
|
||||
#include <android/log.h>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
|
||||
CAndroidAssetFileArchive::CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths) :
|
||||
CFileList("/asset", ignoreCase, ignorePaths), AssetManager(assetManager)
|
||||
{
|
||||
}
|
||||
|
||||
CAndroidAssetFileArchive::~CAndroidAssetFileArchive()
|
||||
{
|
||||
}
|
||||
|
||||
//! get the archive type
|
||||
E_FILE_ARCHIVE_TYPE CAndroidAssetFileArchive::getType() const
|
||||
{
|
||||
return EFAT_ANDROID_ASSET;
|
||||
}
|
||||
|
||||
const IFileList *CAndroidAssetFileArchive::getFileList() const
|
||||
{
|
||||
// The assert_manager can not read directory names, so
|
||||
// getFileList returns only files in folders which have been added.
|
||||
return this;
|
||||
}
|
||||
|
||||
//! opens a file by file name
|
||||
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(const io::path &filename)
|
||||
{
|
||||
CAndroidAssetReader *reader = new CAndroidAssetReader(AssetManager, filename);
|
||||
|
||||
if (reader->isOpen())
|
||||
return reader;
|
||||
|
||||
reader->drop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//! opens a file by index
|
||||
IReadFile *CAndroidAssetFileArchive::createAndOpenFile(u32 index)
|
||||
{
|
||||
const io::path &filename(getFullFileName(index));
|
||||
if (filename.empty())
|
||||
return 0;
|
||||
|
||||
return createAndOpenFile(filename);
|
||||
}
|
||||
|
||||
void CAndroidAssetFileArchive::addDirectoryToFileList(const io::path &dirname_)
|
||||
{
|
||||
io::path dirname(dirname_);
|
||||
fschar_t lastChar = dirname.lastChar();
|
||||
if (lastChar == '/' || lastChar == '\\')
|
||||
dirname.erase(dirname.size() - 1);
|
||||
|
||||
// os::Printer::log("addDirectoryToFileList:", dirname.c_str(), ELL_DEBUG);
|
||||
if (findFile(dirname, true) >= 0)
|
||||
return; // was already added
|
||||
|
||||
AAssetDir *dir = AAssetManager_openDir(AssetManager, core::stringc(dirname).c_str());
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
// add directory itself
|
||||
addItem(dirname, 0, 0, /*isDir*/ true, getFileCount());
|
||||
|
||||
// add all files in folder.
|
||||
// Note: AAssetDir_getNextFileName does not return directory names (neither does any other NDK function)
|
||||
while (const char *filename = AAssetDir_getNextFileName(dir)) {
|
||||
core::stringc full_filename = dirname == "" ? filename
|
||||
: dirname + "/" + filename;
|
||||
|
||||
// We can't get the size without opening the file - so for performance
|
||||
// reasons we set the file size to 0.
|
||||
// TODO: Does this really cost so much performance that it's worth losing this information? Dirs are usually just added once at startup...
|
||||
addItem(full_filename, /*offet*/ 0, /*size*/ 0, /*isDir*/ false, getFileCount());
|
||||
// os::Printer::log("addItem:", full_filename.c_str(), ELL_DEBUG);
|
||||
}
|
||||
AAssetDir_close(dir);
|
||||
}
|
||||
|
||||
} // end namespace io
|
||||
} // end namespace irr
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright (C) 2012 Joerg Henrichs
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IReadFile.h"
|
||||
#include "IFileArchive.h"
|
||||
#include "CFileList.h"
|
||||
|
||||
#include <android/native_activity.h>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
|
||||
/*!
|
||||
Android asset file system written August 2012 by J.Henrichs (later reworked by others).
|
||||
*/
|
||||
class CAndroidAssetFileArchive : public virtual IFileArchive,
|
||||
virtual CFileList
|
||||
{
|
||||
public:
|
||||
//! constructor
|
||||
CAndroidAssetFileArchive(AAssetManager *assetManager, bool ignoreCase, bool ignorePaths);
|
||||
|
||||
//! destructor
|
||||
virtual ~CAndroidAssetFileArchive();
|
||||
|
||||
//! opens a file by file name
|
||||
virtual IReadFile *createAndOpenFile(const io::path &filename);
|
||||
|
||||
//! opens a file by index
|
||||
virtual IReadFile *createAndOpenFile(u32 index);
|
||||
|
||||
//! returns the list of files
|
||||
virtual const IFileList *getFileList() const;
|
||||
|
||||
//! get the archive type
|
||||
virtual E_FILE_ARCHIVE_TYPE getType() const;
|
||||
|
||||
//! Add a directory to read files from. Since the Android
|
||||
//! API does not return names of directories, they need to
|
||||
//! be added manually.
|
||||
virtual void addDirectoryToFileList(const io::path &filename);
|
||||
|
||||
//! return the name (id) of the file Archive
|
||||
const io::path &getArchiveName() const override { return Path; }
|
||||
|
||||
protected:
|
||||
//! Android's asset manager
|
||||
AAssetManager *AssetManager;
|
||||
|
||||
}; // CAndroidAssetFileArchive
|
||||
|
||||
} // end namespace io
|
||||
} // end namespace irr
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "CAndroidAssetReader.h"
|
||||
|
||||
#include "CReadFile.h"
|
||||
#include "coreutil.h"
|
||||
#include "CAndroidAssetReader.h"
|
||||
#include "CIrrDeviceAndroid.h"
|
||||
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/native_activity.h>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
|
||||
CAndroidAssetReader::CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename) :
|
||||
AssetManager(assetManager), Filename(filename)
|
||||
{
|
||||
Asset = AAssetManager_open(AssetManager,
|
||||
core::stringc(filename).c_str(),
|
||||
AASSET_MODE_RANDOM);
|
||||
}
|
||||
|
||||
CAndroidAssetReader::~CAndroidAssetReader()
|
||||
{
|
||||
if (Asset)
|
||||
AAsset_close(Asset);
|
||||
}
|
||||
|
||||
size_t CAndroidAssetReader::read(void *buffer, size_t sizeToRead)
|
||||
{
|
||||
int readBytes = AAsset_read(Asset, buffer, sizeToRead);
|
||||
if (readBytes >= 0)
|
||||
return size_t(readBytes);
|
||||
return 0; // direct fd access is not possible (for example, if the asset is compressed).
|
||||
}
|
||||
|
||||
bool CAndroidAssetReader::seek(long finalPos, bool relativeMovement)
|
||||
{
|
||||
off_t status = AAsset_seek(Asset, finalPos, relativeMovement ? SEEK_CUR : SEEK_SET);
|
||||
|
||||
return status + 1;
|
||||
}
|
||||
|
||||
long CAndroidAssetReader::getSize() const
|
||||
{
|
||||
return AAsset_getLength(Asset);
|
||||
}
|
||||
|
||||
long CAndroidAssetReader::getPos() const
|
||||
{
|
||||
return AAsset_getLength(Asset) - AAsset_getRemainingLength(Asset);
|
||||
}
|
||||
|
||||
const io::path &CAndroidAssetReader::getFileName() const
|
||||
{
|
||||
return Filename;
|
||||
}
|
||||
|
||||
} // end namespace io
|
||||
} // end namespace irr
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (C) 2012 Joerg Henrichs
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IReadFile.h"
|
||||
|
||||
struct AAssetManager;
|
||||
struct AAsset;
|
||||
struct ANativeActivity;
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace io
|
||||
{
|
||||
|
||||
class CAndroidAssetReader : public virtual IReadFile
|
||||
{
|
||||
public:
|
||||
CAndroidAssetReader(AAssetManager *assetManager, const io::path &filename);
|
||||
|
||||
virtual ~CAndroidAssetReader();
|
||||
|
||||
//! Reads an amount of bytes from the file.
|
||||
/** \param buffer Pointer to buffer where read bytes are written to.
|
||||
\param sizeToRead Amount of bytes to read from the file.
|
||||
\return How many bytes were read. */
|
||||
virtual size_t read(void *buffer, size_t sizeToRead);
|
||||
|
||||
//! Changes position in file
|
||||
/** \param finalPos Destination position in the file.
|
||||
\param relativeMovement If set to true, the position in the file is
|
||||
changed relative to current position. Otherwise the position is changed
|
||||
from beginning of file.
|
||||
\return True if successful, otherwise false. */
|
||||
virtual bool seek(long finalPos, bool relativeMovement = false);
|
||||
|
||||
//! Get size of file.
|
||||
/** \return Size of the file in bytes. */
|
||||
virtual long getSize() const;
|
||||
|
||||
//! Get the current position in the file.
|
||||
/** \return Current position in the file in bytes. */
|
||||
virtual long getPos() const;
|
||||
|
||||
//! Get name of file.
|
||||
/** \return File name as zero terminated character string. */
|
||||
virtual const io::path &getFileName() const;
|
||||
|
||||
/** Return true if the file could be opened. */
|
||||
bool isOpen() const { return Asset != NULL; }
|
||||
|
||||
private:
|
||||
//! Android's asset manager
|
||||
AAssetManager *AssetManager;
|
||||
|
||||
// An asset, i.e. file
|
||||
AAsset *Asset;
|
||||
path Filename;
|
||||
};
|
||||
|
||||
} // end namespace io
|
||||
} // end namespace irr
|
|
@ -1,828 +0,0 @@
|
|||
// Copyright (C) 2002-2007 Nikolaus Gebhardt
|
||||
// Copyright (C) 2007-2011 Christian Stehno
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "CIrrDeviceAndroid.h"
|
||||
|
||||
#include "os.h"
|
||||
#include "CFileSystem.h"
|
||||
#include "CAndroidAssetReader.h"
|
||||
#include "CAndroidAssetFileArchive.h"
|
||||
#include "CKeyEventWrapper.h"
|
||||
#include "CEGLManager.h"
|
||||
#include "ISceneManager.h"
|
||||
#include "IGUIEnvironment.h"
|
||||
#include "CEGLManager.h"
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
IVideoDriver *createOGLES1Driver(const SIrrlichtCreationParameters ¶ms,
|
||||
io::IFileSystem *io, video::IContextManager *contextManager);
|
||||
|
||||
IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters ¶ms,
|
||||
io::IFileSystem *io, video::IContextManager *contextManager);
|
||||
}
|
||||
}
|
||||
|
||||
namespace irr
|
||||
{
|
||||
|
||||
CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters ¶m) :
|
||||
CIrrDeviceStub(param), Accelerometer(0), Gyroscope(0), Initialized(false),
|
||||
Stopped(true), Paused(true), Focused(false), JNIEnvAttachedToVM(0)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
setDebugName("CIrrDeviceAndroid");
|
||||
#endif
|
||||
|
||||
// Get the interface to the native Android activity.
|
||||
Android = (android_app *)(param.PrivateData);
|
||||
|
||||
// Set the private data so we can use it in any static callbacks.
|
||||
Android->userData = this;
|
||||
|
||||
// Set the default command handler. This is a callback function that the Android
|
||||
// OS invokes to send the native activity messages.
|
||||
Android->onAppCmd = handleAndroidCommand;
|
||||
|
||||
createKeyMap();
|
||||
|
||||
// Create a sensor manager to receive touch screen events from the java activity.
|
||||
SensorManager = ASensorManager_getInstance();
|
||||
SensorEventQueue = ASensorManager_createEventQueue(SensorManager, Android->looper, LOOPER_ID_USER, 0, 0);
|
||||
Android->onInputEvent = handleInput;
|
||||
|
||||
// Create EGL manager.
|
||||
ContextManager = new video::CEGLManager();
|
||||
|
||||
os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG);
|
||||
|
||||
do {
|
||||
s32 Events = 0;
|
||||
android_poll_source *Source = 0;
|
||||
|
||||
while ((ALooper_pollAll((!Initialized || isWindowActive()) ? 0 : -1, 0, &Events, (void **)&Source)) >= 0) {
|
||||
if (Source)
|
||||
Source->process(Android, Source);
|
||||
}
|
||||
} while (!Initialized);
|
||||
}
|
||||
|
||||
CIrrDeviceAndroid::~CIrrDeviceAndroid()
|
||||
{
|
||||
if (GUIEnvironment) {
|
||||
GUIEnvironment->drop();
|
||||
GUIEnvironment = 0;
|
||||
}
|
||||
|
||||
if (SceneManager) {
|
||||
SceneManager->drop();
|
||||
SceneManager = 0;
|
||||
}
|
||||
|
||||
if (VideoDriver) {
|
||||
VideoDriver->drop();
|
||||
VideoDriver = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::run()
|
||||
{
|
||||
if (!Initialized)
|
||||
return false;
|
||||
|
||||
os::Timer::tick();
|
||||
|
||||
s32 id;
|
||||
s32 Events = 0;
|
||||
android_poll_source *Source = 0;
|
||||
|
||||
while ((id = ALooper_pollAll(0, 0, &Events, (void **)&Source)) >= 0) {
|
||||
if (Source)
|
||||
Source->process(Android, Source);
|
||||
|
||||
// if a sensor has data, we'll process it now.
|
||||
if (id == LOOPER_ID_USER) {
|
||||
ASensorEvent sensorEvent;
|
||||
while (ASensorEventQueue_getEvents(SensorEventQueue, &sensorEvent, 1) > 0) {
|
||||
switch (sensorEvent.type) {
|
||||
case ASENSOR_TYPE_ACCELEROMETER:
|
||||
SEvent accEvent;
|
||||
accEvent.EventType = EET_ACCELEROMETER_EVENT;
|
||||
accEvent.AccelerometerEvent.X = sensorEvent.acceleration.x;
|
||||
accEvent.AccelerometerEvent.Y = sensorEvent.acceleration.y;
|
||||
accEvent.AccelerometerEvent.Z = sensorEvent.acceleration.z;
|
||||
|
||||
postEventFromUser(accEvent);
|
||||
break;
|
||||
|
||||
case ASENSOR_TYPE_GYROSCOPE:
|
||||
SEvent gyroEvent;
|
||||
gyroEvent.EventType = EET_GYROSCOPE_EVENT;
|
||||
gyroEvent.GyroscopeEvent.X = sensorEvent.vector.x;
|
||||
gyroEvent.GyroscopeEvent.Y = sensorEvent.vector.y;
|
||||
gyroEvent.GyroscopeEvent.Z = sensorEvent.vector.z;
|
||||
|
||||
postEventFromUser(gyroEvent);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Initialized)
|
||||
break;
|
||||
}
|
||||
|
||||
return Initialized;
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::yield()
|
||||
{
|
||||
struct timespec ts = {0, 1};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer)
|
||||
{
|
||||
const bool wasStopped = Timer ? Timer->isStopped() : true;
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = (time_t)(timeMs / 1000);
|
||||
ts.tv_nsec = (long)(timeMs % 1000) * 1000000;
|
||||
|
||||
if (pauseTimer && !wasStopped)
|
||||
Timer->stop();
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
if (pauseTimer && !wasStopped)
|
||||
Timer->start();
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::setWindowCaption(const wchar_t *text)
|
||||
{
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isWindowActive() const
|
||||
{
|
||||
return (Focused && !Paused && !Stopped);
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isWindowFocused() const
|
||||
{
|
||||
return Focused;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isWindowMinimized() const
|
||||
{
|
||||
return !Focused;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isWindowVisible() const
|
||||
{
|
||||
return !Stopped;
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::closeDevice()
|
||||
{
|
||||
ANativeActivity_finish(Android->activity);
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::setResizable(bool resize)
|
||||
{
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::minimizeWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::maximizeWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::restoreWindow()
|
||||
{
|
||||
}
|
||||
|
||||
core::position2di CIrrDeviceAndroid::getWindowPosition()
|
||||
{
|
||||
return core::position2di(0, 0);
|
||||
}
|
||||
|
||||
E_DEVICE_TYPE CIrrDeviceAndroid::getType() const
|
||||
{
|
||||
return EIDT_ANDROID;
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::handleAndroidCommand(android_app *app, int32_t cmd)
|
||||
{
|
||||
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
|
||||
|
||||
SEvent event;
|
||||
event.EventType = EET_SYSTEM_EVENT;
|
||||
event.SystemEvent.EventType = ESET_ANDROID_CMD;
|
||||
event.SystemEvent.AndroidCmd.Cmd = cmd;
|
||||
if (device->postEventFromUser(event))
|
||||
return;
|
||||
|
||||
switch (cmd) {
|
||||
case APP_CMD_INPUT_CHANGED:
|
||||
os::Printer::log("Android command APP_CMD_INPUT_CHANGED", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_WINDOW_RESIZED:
|
||||
os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_WINDOW_REDRAW_NEEDED:
|
||||
os::Printer::log("Android command APP_CMD_WINDOW_REDRAW_NEEDED", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_SAVE_STATE:
|
||||
os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_CONTENT_RECT_CHANGED:
|
||||
os::Printer::log("Android command APP_CMD_CONTENT_RECT_CHANGED", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_LOW_MEMORY:
|
||||
os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG);
|
||||
break;
|
||||
case APP_CMD_START:
|
||||
os::Printer::log("Android command APP_CMD_START", ELL_DEBUG);
|
||||
device->Stopped = false;
|
||||
break;
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG);
|
||||
device->getExposedVideoData().OGLESAndroid.Window = app->window;
|
||||
|
||||
if (device->CreationParams.WindowSize.Width == 0 || device->CreationParams.WindowSize.Height == 0) {
|
||||
device->CreationParams.WindowSize.Width = ANativeWindow_getWidth(app->window);
|
||||
device->CreationParams.WindowSize.Height = ANativeWindow_getHeight(app->window);
|
||||
}
|
||||
|
||||
device->getContextManager()->initialize(device->CreationParams, device->ExposedVideoData);
|
||||
device->getContextManager()->generateSurface();
|
||||
device->getContextManager()->generateContext();
|
||||
device->getContextManager()->activateContext(device->getContextManager()->getContext());
|
||||
|
||||
if (!device->Initialized) {
|
||||
io::CAndroidAssetFileArchive *assets = new io::CAndroidAssetFileArchive(device->Android->activity->assetManager, false, false);
|
||||
assets->addDirectoryToFileList("");
|
||||
device->FileSystem->addFileArchive(assets);
|
||||
assets->drop();
|
||||
|
||||
device->createDriver();
|
||||
|
||||
if (device->VideoDriver)
|
||||
device->createGUIAndScene();
|
||||
}
|
||||
device->Initialized = true;
|
||||
break;
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG);
|
||||
device->getContextManager()->destroySurface();
|
||||
break;
|
||||
case APP_CMD_GAINED_FOCUS:
|
||||
os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG);
|
||||
device->Focused = true;
|
||||
break;
|
||||
case APP_CMD_LOST_FOCUS:
|
||||
os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG);
|
||||
device->Focused = false;
|
||||
break;
|
||||
case APP_CMD_DESTROY:
|
||||
os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG);
|
||||
if (device->JNIEnvAttachedToVM) {
|
||||
device->JNIEnvAttachedToVM = 0;
|
||||
device->Android->activity->vm->DetachCurrentThread();
|
||||
}
|
||||
device->Initialized = false;
|
||||
break;
|
||||
case APP_CMD_PAUSE:
|
||||
os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG);
|
||||
device->Paused = true;
|
||||
break;
|
||||
case APP_CMD_STOP:
|
||||
os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG);
|
||||
device->Stopped = true;
|
||||
break;
|
||||
case APP_CMD_RESUME:
|
||||
os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG);
|
||||
device->Paused = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s32 CIrrDeviceAndroid::handleInput(android_app *app, AInputEvent *androidEvent)
|
||||
{
|
||||
CIrrDeviceAndroid *device = (CIrrDeviceAndroid *)app->userData;
|
||||
s32 status = 0;
|
||||
|
||||
switch (AInputEvent_getType(androidEvent)) {
|
||||
case AINPUT_EVENT_TYPE_MOTION: {
|
||||
SEvent event;
|
||||
event.EventType = EET_TOUCH_INPUT_EVENT;
|
||||
|
||||
s32 eventAction = AMotionEvent_getAction(androidEvent);
|
||||
s32 eventType = eventAction & AMOTION_EVENT_ACTION_MASK;
|
||||
|
||||
#if 0
|
||||
// Useful for debugging. We might have to pass some of those infos on at some point.
|
||||
// but preferably device independent (so iphone can use same irrlicht flags).
|
||||
int32_t flags = AMotionEvent_getFlags(androidEvent);
|
||||
os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG);
|
||||
int32_t metaState = AMotionEvent_getMetaState(androidEvent);
|
||||
os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG);
|
||||
int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent);
|
||||
os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
|
||||
bool touchReceived = true;
|
||||
|
||||
switch (eventType) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
event.TouchInput.Event = ETIE_PRESSED_DOWN;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
event.TouchInput.Event = ETIE_MOVED;
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
event.TouchInput.Event = ETIE_LEFT_UP;
|
||||
break;
|
||||
default:
|
||||
touchReceived = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (touchReceived) {
|
||||
// Process all touches for move action.
|
||||
if (event.TouchInput.Event == ETIE_MOVED) {
|
||||
s32 pointerCount = AMotionEvent_getPointerCount(androidEvent);
|
||||
|
||||
for (s32 i = 0; i < pointerCount; ++i) {
|
||||
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
|
||||
event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
|
||||
event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
|
||||
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
|
||||
|
||||
device->postEventFromUser(event);
|
||||
}
|
||||
} else // Process one touch for other actions.
|
||||
{
|
||||
s32 pointerIndex = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
|
||||
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
|
||||
event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
|
||||
event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
|
||||
event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
|
||||
|
||||
device->postEventFromUser(event);
|
||||
}
|
||||
|
||||
status = 1;
|
||||
}
|
||||
} break;
|
||||
case AINPUT_EVENT_TYPE_KEY: {
|
||||
SEvent event;
|
||||
event.EventType = EET_KEY_INPUT_EVENT;
|
||||
|
||||
int32_t keyCode = AKeyEvent_getKeyCode(androidEvent);
|
||||
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
|
||||
|
||||
int32_t keyAction = AKeyEvent_getAction(androidEvent);
|
||||
int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent);
|
||||
|
||||
if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size())
|
||||
event.KeyInput.Key = device->KeyMap[keyCode];
|
||||
else
|
||||
event.KeyInput.Key = KEY_UNKNOWN;
|
||||
event.KeyInput.SystemKeyCode = (u32)keyCode;
|
||||
if (keyAction == AKEY_EVENT_ACTION_DOWN)
|
||||
event.KeyInput.PressedDown = true;
|
||||
else if (keyAction == AKEY_EVENT_ACTION_UP)
|
||||
event.KeyInput.PressedDown = false;
|
||||
else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE) {
|
||||
// TODO: Multiple duplicate key events have occurred in a row,
|
||||
// or a complex string is being delivered. The repeat_count
|
||||
// property of the key event contains the number of times the
|
||||
// given key code should be executed.
|
||||
// I guess this might necessary for more complicated i18n key input,
|
||||
// but don't see yet how to handle this correctly.
|
||||
}
|
||||
|
||||
/* no use for meta keys so far.
|
||||
if ( keyMetaState & AMETA_ALT_ON
|
||||
|| keyMetaState & AMETA_ALT_LEFT_ON
|
||||
|| keyMetaState & AMETA_ALT_RIGHT_ON )
|
||||
;
|
||||
// what is a sym?
|
||||
if ( keyMetaState & AMETA_SYM_ON )
|
||||
;
|
||||
*/
|
||||
if (keyMetaState & AMETA_SHIFT_ON || keyMetaState & AMETA_SHIFT_LEFT_ON || keyMetaState & AMETA_SHIFT_RIGHT_ON)
|
||||
event.KeyInput.Shift = true;
|
||||
else
|
||||
event.KeyInput.Shift = false;
|
||||
event.KeyInput.Control = false;
|
||||
|
||||
// Having memory allocations + going through JNI for each key-press is pretty bad (slow).
|
||||
// So we do it only for those keys which are likely text-characters and avoid it for all other keys.
|
||||
// So it's fast for keys like game controller input and special keys. And text keys are typically
|
||||
// only used or entering text and not for gaming on Android, so speed likely doesn't matter there too much.
|
||||
if (event.KeyInput.Key > 0) {
|
||||
// TODO:
|
||||
// Not sure why we have to attach a JNIEnv here, but it won't work when doing that in the constructor or
|
||||
// trying to use the activity->env. My best guess is that the event-handling happens in an own thread.
|
||||
// It means JNIEnvAttachedToVM will never get detached as I don't know a safe way where to do that
|
||||
// (we could attach & detach each time, but that would probably be slow)
|
||||
// Also - it has to be each time as it get's invalid when the application mode changes.
|
||||
if (device->Initialized && device->Android && device->Android->activity && device->Android->activity->vm) {
|
||||
JavaVMAttachArgs attachArgs;
|
||||
attachArgs.version = JNI_VERSION_1_6;
|
||||
attachArgs.name = 0;
|
||||
attachArgs.group = NULL;
|
||||
|
||||
// Not a big problem calling it each time - it's a no-op when the thread already is attached.
|
||||
// And we have to do that as someone else can have detached the thread in the meantime.
|
||||
jint result = device->Android->activity->vm->AttachCurrentThread(&device->JNIEnvAttachedToVM, &attachArgs);
|
||||
if (result == JNI_ERR) {
|
||||
os::Printer::log("AttachCurrentThread for the JNI environment failed.", ELL_WARNING);
|
||||
device->JNIEnvAttachedToVM = 0;
|
||||
}
|
||||
|
||||
if (device->JNIEnvAttachedToVM) {
|
||||
jni::CKeyEventWrapper *keyEventWrapper = new jni::CKeyEventWrapper(device->JNIEnvAttachedToVM, keyAction, keyCode);
|
||||
event.KeyInput.Char = keyEventWrapper->getUnicodeChar(keyMetaState);
|
||||
delete keyEventWrapper;
|
||||
}
|
||||
}
|
||||
if (event.KeyInput.Key == KEY_BACK) {
|
||||
event.KeyInput.Char = 0x08; // same key-code as on other operating systems. Otherwise we have to handle too much system specific stuff in the editbox.
|
||||
}
|
||||
// os::Printer::log("char-code: ", core::stringc((int)event.KeyInput.Char).c_str(), ELL_DEBUG);
|
||||
|
||||
} else {
|
||||
// os::Printer::log("keyCode: ", core::stringc(keyCode).c_str(), ELL_DEBUG);
|
||||
event.KeyInput.Char = 0;
|
||||
}
|
||||
|
||||
status = device->postEventFromUser(event);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::createDriver()
|
||||
{
|
||||
switch (CreationParams.DriverType) {
|
||||
case video::EDT_OGLES1:
|
||||
#ifdef _IRR_COMPILE_WITH_OGLES1_
|
||||
VideoDriver = video::createOGLES1Driver(CreationParams, FileSystem, ContextManager);
|
||||
#else
|
||||
os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR);
|
||||
#endif
|
||||
break;
|
||||
case video::EDT_OGLES2:
|
||||
#ifdef _IRR_COMPILE_WITH_OGLES2_
|
||||
VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);
|
||||
#else
|
||||
os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR);
|
||||
#endif
|
||||
break;
|
||||
case video::EDT_NULL:
|
||||
VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
|
||||
break;
|
||||
case video::EDT_OPENGL:
|
||||
os::Printer::log("This driver is not available in Android. Try OpenGL ES 1.0 or ES 2.0.", ELL_ERROR);
|
||||
break;
|
||||
default:
|
||||
os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
video::SExposedVideoData &CIrrDeviceAndroid::getExposedVideoData()
|
||||
{
|
||||
return ExposedVideoData;
|
||||
}
|
||||
|
||||
void CIrrDeviceAndroid::createKeyMap()
|
||||
{
|
||||
KeyMap.set_used(223);
|
||||
|
||||
KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN
|
||||
KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
|
||||
KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
|
||||
KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
|
||||
KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
|
||||
KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
|
||||
KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
|
||||
KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
|
||||
KeyMap[8] = KEY_KEY_1; // AKEYCODE_1
|
||||
KeyMap[9] = KEY_KEY_2; // AKEYCODE_2
|
||||
KeyMap[10] = KEY_KEY_3; // AKEYCODE_3
|
||||
KeyMap[11] = KEY_KEY_4; // AKEYCODE_4
|
||||
KeyMap[12] = KEY_KEY_5; // AKEYCODE_5
|
||||
KeyMap[13] = KEY_KEY_6; // AKEYCODE_6
|
||||
KeyMap[14] = KEY_KEY_7; // AKEYCODE_7
|
||||
KeyMap[15] = KEY_KEY_8; // AKEYCODE_8
|
||||
KeyMap[16] = KEY_KEY_9; // AKEYCODE_9
|
||||
KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR
|
||||
KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND
|
||||
KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP
|
||||
KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN
|
||||
KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT
|
||||
KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT
|
||||
KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER
|
||||
KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP
|
||||
KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN
|
||||
KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER
|
||||
KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA
|
||||
KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR
|
||||
KeyMap[29] = KEY_KEY_A; // AKEYCODE_A
|
||||
KeyMap[30] = KEY_KEY_B; // AKEYCODE_B
|
||||
KeyMap[31] = KEY_KEY_C; // AKEYCODE_C
|
||||
KeyMap[32] = KEY_KEY_D; // AKEYCODE_D
|
||||
KeyMap[33] = KEY_KEY_E; // AKEYCODE_E
|
||||
KeyMap[34] = KEY_KEY_F; // AKEYCODE_F
|
||||
KeyMap[35] = KEY_KEY_G; // AKEYCODE_G
|
||||
KeyMap[36] = KEY_KEY_H; // AKEYCODE_H
|
||||
KeyMap[37] = KEY_KEY_I; // AKEYCODE_I
|
||||
KeyMap[38] = KEY_KEY_J; // AKEYCODE_J
|
||||
KeyMap[39] = KEY_KEY_K; // AKEYCODE_K
|
||||
KeyMap[40] = KEY_KEY_L; // AKEYCODE_L
|
||||
KeyMap[41] = KEY_KEY_M; // AKEYCODE_M
|
||||
KeyMap[42] = KEY_KEY_N; // AKEYCODE_N
|
||||
KeyMap[43] = KEY_KEY_O; // AKEYCODE_O
|
||||
KeyMap[44] = KEY_KEY_P; // AKEYCODE_P
|
||||
KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q
|
||||
KeyMap[46] = KEY_KEY_R; // AKEYCODE_R
|
||||
KeyMap[47] = KEY_KEY_S; // AKEYCODE_S
|
||||
KeyMap[48] = KEY_KEY_T; // AKEYCODE_T
|
||||
KeyMap[49] = KEY_KEY_U; // AKEYCODE_U
|
||||
KeyMap[50] = KEY_KEY_V; // AKEYCODE_V
|
||||
KeyMap[51] = KEY_KEY_W; // AKEYCODE_W
|
||||
KeyMap[52] = KEY_KEY_X; // AKEYCODE_X
|
||||
KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y
|
||||
KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z
|
||||
KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA
|
||||
KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD
|
||||
KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT
|
||||
KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT
|
||||
KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT
|
||||
KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT
|
||||
KeyMap[61] = KEY_TAB; // AKEYCODE_TAB
|
||||
KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE
|
||||
KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM
|
||||
KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER
|
||||
KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE
|
||||
KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER
|
||||
KeyMap[67] = KEY_BACK; // AKEYCODE_DEL
|
||||
KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE
|
||||
KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS
|
||||
KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS
|
||||
KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET
|
||||
KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET
|
||||
KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH
|
||||
KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON
|
||||
KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE
|
||||
KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH
|
||||
KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT
|
||||
KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM
|
||||
KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK
|
||||
KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus)
|
||||
KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS
|
||||
KeyMap[82] = KEY_MENU; // AKEYCODE_MENU
|
||||
KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION
|
||||
KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH
|
||||
KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE
|
||||
KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP
|
||||
KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT
|
||||
KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS
|
||||
KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND
|
||||
KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD
|
||||
KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE
|
||||
KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP
|
||||
KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN
|
||||
KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS
|
||||
KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET
|
||||
|
||||
// following look like controller inputs
|
||||
KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A
|
||||
KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B
|
||||
KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C
|
||||
KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X
|
||||
KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y
|
||||
KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z
|
||||
KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1
|
||||
KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1
|
||||
KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2
|
||||
KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2
|
||||
KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL
|
||||
KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR
|
||||
KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START
|
||||
KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT
|
||||
KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE
|
||||
|
||||
KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE
|
||||
KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL
|
||||
KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT
|
||||
KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT
|
||||
KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK
|
||||
KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK
|
||||
KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT
|
||||
KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT
|
||||
KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION
|
||||
KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ
|
||||
KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK
|
||||
KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME
|
||||
KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END
|
||||
KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT
|
||||
KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD
|
||||
KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY
|
||||
KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE
|
||||
KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE
|
||||
KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT
|
||||
KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD
|
||||
KeyMap[131] = KEY_F1; // AKEYCODE_F1
|
||||
KeyMap[132] = KEY_F2; // AKEYCODE_F2
|
||||
KeyMap[133] = KEY_F3; // AKEYCODE_F3
|
||||
KeyMap[134] = KEY_F4; // AKEYCODE_F4
|
||||
KeyMap[135] = KEY_F5; // AKEYCODE_F5
|
||||
KeyMap[136] = KEY_F6; // AKEYCODE_F6
|
||||
KeyMap[137] = KEY_F7; // AKEYCODE_F7
|
||||
KeyMap[138] = KEY_F8; // AKEYCODE_F8
|
||||
KeyMap[139] = KEY_F9; // AKEYCODE_F9
|
||||
KeyMap[140] = KEY_F10; // AKEYCODE_F10
|
||||
KeyMap[141] = KEY_F11; // AKEYCODE_F11
|
||||
KeyMap[142] = KEY_F12; // AKEYCODE_F12
|
||||
KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK
|
||||
KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0
|
||||
KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1
|
||||
KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2
|
||||
KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3
|
||||
KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4
|
||||
KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5
|
||||
KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6
|
||||
KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7
|
||||
KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8
|
||||
KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9
|
||||
KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE
|
||||
KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY
|
||||
KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT
|
||||
KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD
|
||||
KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT
|
||||
KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA
|
||||
KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER
|
||||
KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS
|
||||
KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN
|
||||
KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN
|
||||
KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE
|
||||
KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO
|
||||
KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP
|
||||
KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN
|
||||
KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN
|
||||
KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT
|
||||
KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV
|
||||
KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW
|
||||
KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE
|
||||
KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR
|
||||
KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK
|
||||
KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS
|
||||
KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS
|
||||
KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER
|
||||
KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT
|
||||
KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER
|
||||
KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT
|
||||
KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER
|
||||
KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT
|
||||
KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED
|
||||
KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN
|
||||
KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW
|
||||
KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE
|
||||
KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH
|
||||
KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1
|
||||
KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2
|
||||
KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3
|
||||
KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4
|
||||
KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5
|
||||
KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6
|
||||
KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7
|
||||
KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8
|
||||
KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9
|
||||
KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10
|
||||
KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11
|
||||
KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12
|
||||
KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13
|
||||
KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14
|
||||
KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15
|
||||
KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16
|
||||
KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH
|
||||
KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE
|
||||
KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE
|
||||
KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS
|
||||
KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR
|
||||
KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC
|
||||
KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR
|
||||
KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU
|
||||
KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU
|
||||
KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN
|
||||
KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN
|
||||
KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA
|
||||
KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN
|
||||
KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO
|
||||
KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA
|
||||
KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST
|
||||
KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN
|
||||
KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP ,
|
||||
KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval)
|
||||
{
|
||||
if (!isAccelerometerAvailable())
|
||||
return false;
|
||||
|
||||
ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer);
|
||||
ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
|
||||
|
||||
os::Printer::log("Activated accelerometer", ELL_DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::deactivateAccelerometer()
|
||||
{
|
||||
if (Accelerometer) {
|
||||
ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer);
|
||||
Accelerometer = 0;
|
||||
os::Printer::log("Deactivated accelerometer", ELL_DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isAccelerometerActive()
|
||||
{
|
||||
return (Accelerometer != 0);
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isAccelerometerAvailable()
|
||||
{
|
||||
if (!Accelerometer)
|
||||
Accelerometer = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_ACCELEROMETER);
|
||||
|
||||
return (Accelerometer != 0);
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::activateGyroscope(float updateInterval)
|
||||
{
|
||||
if (!isGyroscopeAvailable())
|
||||
return false;
|
||||
|
||||
ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope);
|
||||
ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, (int32_t)(updateInterval * 1000.f * 1000.f)); // in microseconds
|
||||
|
||||
os::Printer::log("Activated gyroscope", ELL_DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::deactivateGyroscope()
|
||||
{
|
||||
if (Gyroscope) {
|
||||
ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope);
|
||||
Gyroscope = 0;
|
||||
os::Printer::log("Deactivated gyroscope", ELL_DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isGyroscopeActive()
|
||||
{
|
||||
return (Gyroscope != 0);
|
||||
}
|
||||
|
||||
bool CIrrDeviceAndroid::isGyroscopeAvailable()
|
||||
{
|
||||
if (!Gyroscope)
|
||||
Gyroscope = ASensorManager_getDefaultSensor(SensorManager, ASENSOR_TYPE_GYROSCOPE);
|
||||
|
||||
return (Gyroscope != 0);
|
||||
}
|
||||
|
||||
} // end namespace irr
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright (C) 2002-2011 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CIrrDeviceStub.h"
|
||||
#include "IrrlichtDevice.h"
|
||||
#include "ICursorControl.h"
|
||||
|
||||
#include <android/sensor.h>
|
||||
#include <android_native_app_glue.h>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
class CIrrDeviceAndroid : public CIrrDeviceStub
|
||||
{
|
||||
public:
|
||||
CIrrDeviceAndroid(const SIrrlichtCreationParameters ¶m);
|
||||
|
||||
virtual ~CIrrDeviceAndroid();
|
||||
|
||||
virtual bool run();
|
||||
|
||||
virtual void yield();
|
||||
|
||||
virtual void sleep(u32 timeMs, bool pauseTimer = false);
|
||||
|
||||
virtual void setWindowCaption(const wchar_t *text);
|
||||
|
||||
virtual bool isWindowActive() const;
|
||||
|
||||
virtual bool isWindowFocused() const;
|
||||
|
||||
virtual bool isWindowMinimized() const;
|
||||
|
||||
virtual bool isWindowVisible() const;
|
||||
|
||||
virtual void closeDevice();
|
||||
|
||||
virtual void setResizable(bool resize = false);
|
||||
|
||||
virtual void minimizeWindow();
|
||||
|
||||
virtual void maximizeWindow();
|
||||
|
||||
virtual void restoreWindow();
|
||||
|
||||
virtual core::position2di getWindowPosition();
|
||||
|
||||
virtual E_DEVICE_TYPE getType() const;
|
||||
|
||||
virtual bool activateAccelerometer(float updateInterval);
|
||||
|
||||
virtual bool deactivateAccelerometer();
|
||||
|
||||
virtual bool isAccelerometerActive();
|
||||
|
||||
virtual bool isAccelerometerAvailable();
|
||||
|
||||
virtual bool activateGyroscope(float updateInterval);
|
||||
|
||||
virtual bool deactivateGyroscope();
|
||||
|
||||
virtual bool isGyroscopeActive();
|
||||
|
||||
virtual bool isGyroscopeAvailable();
|
||||
|
||||
private:
|
||||
static void handleAndroidCommand(android_app *app, int32_t cmd);
|
||||
|
||||
static s32 handleInput(android_app *app, AInputEvent *event);
|
||||
|
||||
void createDriver();
|
||||
|
||||
void createKeyMap();
|
||||
|
||||
video::SExposedVideoData &getExposedVideoData();
|
||||
|
||||
android_app *Android;
|
||||
ASensorManager *SensorManager;
|
||||
ASensorEventQueue *SensorEventQueue;
|
||||
const ASensor *Accelerometer;
|
||||
const ASensor *Gyroscope;
|
||||
|
||||
bool Initialized;
|
||||
bool Stopped;
|
||||
bool Paused;
|
||||
bool Focused;
|
||||
|
||||
JNIEnv *JNIEnvAttachedToVM;
|
||||
|
||||
video::SExposedVideoData ExposedVideoData;
|
||||
|
||||
core::array<EKEY_CODE> KeyMap;
|
||||
};
|
||||
|
||||
} // end namespace irr
|
|
@ -1,52 +0,0 @@
|
|||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "CKeyEventWrapper.h"
|
||||
|
||||
#include "os.h"
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace jni
|
||||
{
|
||||
|
||||
jclass CKeyEventWrapper::Class_KeyEvent = 0;
|
||||
jmethodID CKeyEventWrapper::Method_constructor = 0;
|
||||
jmethodID CKeyEventWrapper::Method_getUnicodeChar = 0;
|
||||
|
||||
CKeyEventWrapper::CKeyEventWrapper(JNIEnv *jniEnv, int action, int code) :
|
||||
JniEnv(jniEnv), JniKeyEvent(0)
|
||||
{
|
||||
if (JniEnv) {
|
||||
if (!Class_KeyEvent) {
|
||||
// Find java classes & functions on first call
|
||||
os::Printer::log("CKeyEventWrapper first initialize", ELL_DEBUG);
|
||||
jclass localClass = JniEnv->FindClass("android/view/KeyEvent");
|
||||
if (localClass) {
|
||||
Class_KeyEvent = reinterpret_cast<jclass>(JniEnv->NewGlobalRef(localClass));
|
||||
}
|
||||
|
||||
Method_constructor = JniEnv->GetMethodID(Class_KeyEvent, "<init>", "(II)V");
|
||||
Method_getUnicodeChar = JniEnv->GetMethodID(Class_KeyEvent, "getUnicodeChar", "(I)I");
|
||||
}
|
||||
|
||||
if (Class_KeyEvent && Method_constructor) {
|
||||
JniKeyEvent = JniEnv->NewObject(Class_KeyEvent, Method_constructor, action, code);
|
||||
} else {
|
||||
os::Printer::log("CKeyEventWrapper didn't find JNI classes/methods", ELL_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CKeyEventWrapper::~CKeyEventWrapper()
|
||||
{
|
||||
JniEnv->DeleteLocalRef(JniKeyEvent);
|
||||
}
|
||||
|
||||
int CKeyEventWrapper::getUnicodeChar(int metaState)
|
||||
{
|
||||
return JniEnv->CallIntMethod(JniKeyEvent, Method_getUnicodeChar, metaState);
|
||||
}
|
||||
|
||||
} // namespace jni
|
||||
} // namespace irr
|
|
@ -1,35 +0,0 @@
|
|||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
struct android_app;
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace jni
|
||||
{
|
||||
|
||||
//! Minimal JNI wrapper class around android.view.KeyEvent
|
||||
//! NOTE: Only functions we actually use in the engine are wrapped
|
||||
//! This is currently not written to support multithreading - meaning threads are not attached/detached to the Java VM (to be discussed)
|
||||
class CKeyEventWrapper
|
||||
{
|
||||
public:
|
||||
CKeyEventWrapper(JNIEnv *jniEnv, int action, int code);
|
||||
~CKeyEventWrapper();
|
||||
|
||||
int getUnicodeChar(int metaState);
|
||||
|
||||
private:
|
||||
static jclass Class_KeyEvent;
|
||||
static jmethodID Method_getUnicodeChar;
|
||||
static jmethodID Method_constructor;
|
||||
JNIEnv *JniEnv;
|
||||
jobject JniKeyEvent; // this object in java
|
||||
};
|
||||
|
||||
} // namespace jni
|
||||
} // namespace irr
|
|
@ -10,10 +10,6 @@
|
|||
#include "irrArray.h"
|
||||
#include "os.h"
|
||||
|
||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
||||
#include <android/native_activity.h>
|
||||
#endif
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace video
|
||||
|
@ -55,9 +51,6 @@ bool CEGLManager::initialize(const SIrrlichtCreationParameters ¶ms, const SE
|
|||
#elif defined(_IRR_COMPILE_WITH_X11_DEVICE_)
|
||||
EglWindow = (NativeWindowType)Data.OpenGLLinux.X11Window;
|
||||
EglDisplay = eglGetDisplay((NativeDisplayType)Data.OpenGLLinux.X11Display);
|
||||
#elif defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
||||
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
|
||||
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
#elif defined(_IRR_COMPILE_WITH_FB_DEVICE_)
|
||||
EglWindow = (NativeWindowType)Data.OpenGLFB.Window;
|
||||
EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
|
@ -119,10 +112,6 @@ bool CEGLManager::generateSurface()
|
|||
// at this time only Android support this feature.
|
||||
// this needs an update method instead!
|
||||
|
||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
||||
EglWindow = (ANativeWindow *)Data.OGLESAndroid.Window;
|
||||
#endif
|
||||
|
||||
#if defined(_IRR_EMSCRIPTEN_PLATFORM_)
|
||||
// eglChooseConfig is currently only implemented as stub in emscripten (version 1.37.22 at point of writing)
|
||||
// But the other solution would also be fine as it also only generates a single context so there is not much to choose from.
|
||||
|
@ -136,13 +125,6 @@ bool CEGLManager::generateSurface()
|
|||
return false;
|
||||
}
|
||||
|
||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
|
||||
EGLint Format = 0;
|
||||
eglGetConfigAttrib(EglDisplay, EglConfig, EGL_NATIVE_VISUAL_ID, &Format);
|
||||
|
||||
ANativeWindow_setBuffersGeometry(EglWindow, 0, 0, Format);
|
||||
#endif
|
||||
|
||||
// Now we are able to create EGL surface.
|
||||
EglSurface = eglCreateWindowSurface(EglDisplay, EglConfig, EglWindow, 0);
|
||||
|
||||
|
|
|
@ -18,14 +18,20 @@ namespace video
|
|||
// PNG function for error handling
|
||||
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
||||
{
|
||||
os::Printer::log("PNG fatal error", msg, ELL_ERROR);
|
||||
io::IReadFile *file = reinterpret_cast<io::IReadFile *>(png_get_error_ptr(png_ptr));
|
||||
std::string logmsg = std::string("PNG fatal error for ")
|
||||
+ file->getFileName().c_str() + ": " + msg;
|
||||
os::Printer::log(logmsg.c_str(), ELL_ERROR);
|
||||
longjmp(png_jmpbuf(png_ptr), 1);
|
||||
}
|
||||
|
||||
// PNG function for warning handling
|
||||
static void png_cpexcept_warn(png_structp png_ptr, png_const_charp msg)
|
||||
{
|
||||
os::Printer::log("PNG warning", msg, ELL_WARNING);
|
||||
io::IReadFile *file = reinterpret_cast<io::IReadFile *>(png_get_error_ptr(png_ptr));
|
||||
std::string logmsg = std::string("PNG warning for ")
|
||||
+ file->getFileName().c_str() + ": " + msg;
|
||||
os::Printer::log(logmsg.c_str(), ELL_WARNING);
|
||||
}
|
||||
|
||||
// PNG function for file reading
|
||||
|
@ -88,7 +94,7 @@ IImage *CImageLoaderPng::loadImage(io::IReadFile *file) const
|
|||
|
||||
// Allocate the png read struct
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||
NULL, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warn);
|
||||
file, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warn);
|
||||
if (!png_ptr) {
|
||||
os::Printer::log("LOAD PNG: Internal PNG create read struct failure", file->getFileName(), ELL_ERROR);
|
||||
return 0;
|
||||
|
|
|
@ -25,14 +25,20 @@ IImageWriter *createImageWriterPNG()
|
|||
// PNG function for error handling
|
||||
static void png_cpexcept_error(png_structp png_ptr, png_const_charp msg)
|
||||
{
|
||||
os::Printer::log("PNG fatal error", msg, ELL_ERROR);
|
||||
io::IWriteFile *file = reinterpret_cast<io::IWriteFile *>(png_get_error_ptr(png_ptr));
|
||||
std::string logmsg = std::string("PNG fatal error for ")
|
||||
+ file->getFileName().c_str() + ": " + msg;
|
||||
os::Printer::log(logmsg.c_str(), ELL_ERROR);
|
||||
longjmp(png_jmpbuf(png_ptr), 1);
|
||||
}
|
||||
|
||||
// PNG function for warning handling
|
||||
static void png_cpexcept_warning(png_structp png_ptr, png_const_charp msg)
|
||||
{
|
||||
os::Printer::log("PNG warning", msg, ELL_WARNING);
|
||||
io::IWriteFile *file = reinterpret_cast<io::IWriteFile *>(png_get_error_ptr(png_ptr));
|
||||
std::string logmsg = std::string("PNG warning for ")
|
||||
+ file->getFileName().c_str() + ": " + msg;
|
||||
os::Printer::log(logmsg.c_str(), ELL_WARNING);
|
||||
}
|
||||
|
||||
// PNG function for file writing
|
||||
|
@ -66,7 +72,7 @@ bool CImageWriterPNG::writeImage(io::IWriteFile *file, IImage *image, u32 param)
|
|||
|
||||
// Allocate the png write struct
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||
NULL, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warning);
|
||||
file, (png_error_ptr)png_cpexcept_error, (png_error_ptr)png_cpexcept_warning);
|
||||
if (!png_ptr) {
|
||||
os::Printer::log("PNGWriter: Internal PNG create write struct failure", file->getFileName(), ELL_ERROR);
|
||||
return false;
|
||||
|
|
|
@ -249,16 +249,34 @@ void CIrrDeviceSDL::resetReceiveTextInputEvents()
|
|||
//! constructor
|
||||
CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
||||
CIrrDeviceStub(param),
|
||||
Window((SDL_Window *)param.WindowId), SDL_Flags(0),
|
||||
Window((SDL_Window *)param.WindowId),
|
||||
MouseX(0), MouseY(0), MouseXRel(0), MouseYRel(0), MouseButtonStates(0),
|
||||
Width(param.WindowSize.Width), Height(param.WindowSize.Height),
|
||||
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0)
|
||||
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0),
|
||||
IsInBackground(false)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
setDebugName("CIrrDeviceSDL");
|
||||
#endif
|
||||
|
||||
if (++SDLDeviceInstances == 1) {
|
||||
#ifdef __ANDROID__
|
||||
// Blocking on pause causes problems with multiplayer.
|
||||
// See https://github.com/minetest/minetest/issues/10842.
|
||||
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0");
|
||||
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0");
|
||||
|
||||
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
|
||||
|
||||
// Minetest does its own screen keyboard handling.
|
||||
SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
|
||||
#endif
|
||||
|
||||
// Minetest has its own code to synthesize mouse events from touch events,
|
||||
// so we prevent SDL from doing it.
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||
|
||||
u32 flags = SDL_INIT_TIMER | SDL_INIT_EVENTS;
|
||||
if (CreationParams.DriverType != video::EDT_NULL)
|
||||
flags |= SDL_INIT_VIDEO;
|
||||
|
@ -273,11 +291,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
|||
}
|
||||
}
|
||||
|
||||
// Minetest has its own code to synthesize mouse events from touch events,
|
||||
// so we prevent SDL from doing it.
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||
|
||||
// create keymap
|
||||
createKeyMap();
|
||||
|
||||
|
@ -321,19 +334,21 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) :
|
|||
//! destructor
|
||||
CIrrDeviceSDL::~CIrrDeviceSDL()
|
||||
{
|
||||
if (--SDLDeviceInstances == 0) {
|
||||
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
||||
const u32 numJoysticks = Joysticks.size();
|
||||
for (u32 i = 0; i < numJoysticks; ++i)
|
||||
SDL_JoystickClose(Joysticks[i]);
|
||||
const u32 numJoysticks = Joysticks.size();
|
||||
for (u32 i = 0; i < numJoysticks; ++i)
|
||||
SDL_JoystickClose(Joysticks[i]);
|
||||
#endif
|
||||
if (Window) {
|
||||
SDL_GL_MakeCurrent(Window, NULL);
|
||||
SDL_GL_DeleteContext(Context);
|
||||
SDL_DestroyWindow(Window);
|
||||
}
|
||||
SDL_Quit();
|
||||
if (Window && Context) {
|
||||
SDL_GL_MakeCurrent(Window, NULL);
|
||||
SDL_GL_DeleteContext(Context);
|
||||
}
|
||||
if (Window) {
|
||||
SDL_DestroyWindow(Window);
|
||||
}
|
||||
|
||||
if (--SDLDeviceInstances == 0) {
|
||||
SDL_Quit();
|
||||
os::Printer::log("Quit SDL", ELL_INFORMATION);
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +382,76 @@ void CIrrDeviceSDL::logAttributes()
|
|||
|
||||
bool CIrrDeviceSDL::createWindow()
|
||||
{
|
||||
if (Close)
|
||||
return false;
|
||||
|
||||
if (createWindowWithContext())
|
||||
return true;
|
||||
|
||||
while (CreationParams.AntiAlias > 0) {
|
||||
CreationParams.AntiAlias--;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("AntiAlias reduced/disabled due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CreationParams.WithAlphaChannel) {
|
||||
CreationParams.WithAlphaChannel = false;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("WithAlphaChannel disabled due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CreationParams.Stencilbuffer) {
|
||||
CreationParams.Stencilbuffer = false;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("Stencilbuffer disabled due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (CreationParams.ZBufferBits > 16) {
|
||||
CreationParams.ZBufferBits -= 8;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("ZBufferBits reduced due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (CreationParams.Bits > 16) {
|
||||
CreationParams.Bits -= 8;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("Bits reduced due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CreationParams.Stereobuffer) {
|
||||
CreationParams.Stereobuffer = false;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("Stereobuffer disabled due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CreationParams.Doublebuffer) {
|
||||
// Try single buffer
|
||||
CreationParams.Doublebuffer = false;
|
||||
if (createWindowWithContext()) {
|
||||
os::Printer::log("Doublebuffer disabled due to lack of support!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
os::Printer::log("Could not create window and context!", ELL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CIrrDeviceSDL::createWindowWithContext() {
|
||||
u32 SDL_Flags = 0;
|
||||
|
||||
if (CreationParams.Fullscreen) {
|
||||
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
|
||||
SDL_Flags |= SDL_WINDOW_FULLSCREEN;
|
||||
|
@ -419,9 +504,6 @@ bool CIrrDeviceSDL::createWindow()
|
|||
|
||||
return true;
|
||||
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||
if (Close)
|
||||
return false;
|
||||
|
||||
switch (CreationParams.DriverType) {
|
||||
case video::EDT_OPENGL:
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
|
@ -446,9 +528,13 @@ bool CIrrDeviceSDL::createWindow()
|
|||
default:;
|
||||
}
|
||||
|
||||
/*
|
||||
Makes context creation fail on some Android devices.
|
||||
See discussion in https://github.com/minetest/minetest/pull/14498.
|
||||
#ifdef _DEBUG
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
|
||||
#endif
|
||||
*/
|
||||
|
||||
if (CreationParams.Bits == 16) {
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||
|
@ -468,46 +554,33 @@ bool CIrrDeviceSDL::createWindow()
|
|||
if (CreationParams.AntiAlias > 1) {
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
|
||||
}
|
||||
if (!Window)
|
||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||
if (!Window) {
|
||||
os::Printer::log("Could not create window...", SDL_GetError(), ELL_WARNING);
|
||||
}
|
||||
if (!Window && CreationParams.AntiAlias > 1) {
|
||||
while (--CreationParams.AntiAlias > 1) {
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
|
||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||
if (Window)
|
||||
break;
|
||||
}
|
||||
if (!Window) {
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
|
||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||
if (Window)
|
||||
os::Printer::log("AntiAliasing disabled due to lack of support!", ELL_WARNING);
|
||||
}
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
|
||||
}
|
||||
|
||||
if (!Window && CreationParams.Doublebuffer) {
|
||||
// Try single buffer
|
||||
if (CreationParams.DriverType == video::EDT_OPENGL)
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||
}
|
||||
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
|
||||
if (!Window) {
|
||||
os::Printer::log("Could not initialize display", SDL_GetError(), ELL_ERROR);
|
||||
os::Printer::log("Could not create window", SDL_GetError(), ELL_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
Context = SDL_GL_CreateContext(Window);
|
||||
if (!Context) {
|
||||
os::Printer::log("Could not initialize context", SDL_GetError(), ELL_ERROR);
|
||||
os::Printer::log("Could not create context", SDL_GetError(), ELL_WARNING);
|
||||
SDL_DestroyWindow(Window);
|
||||
Window = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update Width and Height to match the actual window size.
|
||||
// In fullscreen mode, the window size specified in SIrrlichtCreationParameters
|
||||
// is ignored, so we cannot rely on it.
|
||||
int w = 0, h = 0;
|
||||
SDL_GetWindowSize(Window, &w, &h);
|
||||
Width = w;
|
||||
Height = h;
|
||||
|
||||
return true;
|
||||
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||
}
|
||||
|
@ -619,7 +692,17 @@ bool CIrrDeviceSDL::run()
|
|||
}
|
||||
#endif
|
||||
|
||||
switch (SDL_event.button.button) {
|
||||
auto button = SDL_event.button.button;
|
||||
#ifdef __ANDROID__
|
||||
// Android likes to send the right mouse button as the back button.
|
||||
// According to some web searches I did, this is probably
|
||||
// vendor/device-specific.
|
||||
// Since a working right mouse button is very important for
|
||||
// Minetest, we have this little hack.
|
||||
if (button == SDL_BUTTON_X2)
|
||||
button = SDL_BUTTON_RIGHT;
|
||||
#endif
|
||||
switch (button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
|
||||
|
@ -770,6 +853,20 @@ bool CIrrDeviceSDL::run()
|
|||
postEventFromUser(irrevent);
|
||||
break;
|
||||
|
||||
// Contrary to what the SDL documentation says, SDL_APP_WILLENTERBACKGROUND
|
||||
// and SDL_APP_WILLENTERFOREGROUND are actually sent in onStop/onStart,
|
||||
// not onPause/onResume, on recent Android versions. This can be verified
|
||||
// by testing or by looking at the org.libsdl.app.SDLActivity Java code.
|
||||
// -> This means we can use them to implement isWindowVisible().
|
||||
|
||||
case SDL_APP_WILLENTERBACKGROUND:
|
||||
IsInBackground = true;
|
||||
break;
|
||||
|
||||
case SDL_APP_WILLENTERFOREGROUND:
|
||||
IsInBackground = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
} // end switch
|
||||
|
@ -996,11 +1093,6 @@ void CIrrDeviceSDL::setResizable(bool resize)
|
|||
return;
|
||||
#else // !_IRR_EMSCRIPTEN_PLATFORM_
|
||||
if (resize != Resizable) {
|
||||
if (resize)
|
||||
SDL_Flags |= SDL_WINDOW_RESIZABLE;
|
||||
else
|
||||
SDL_Flags &= ~SDL_WINDOW_RESIZABLE;
|
||||
|
||||
if (Window) {
|
||||
SDL_SetWindowResizable(Window, (SDL_bool)resize);
|
||||
}
|
||||
|
@ -1051,6 +1143,11 @@ bool CIrrDeviceSDL::isFullscreen() const
|
|||
#endif
|
||||
}
|
||||
|
||||
bool CIrrDeviceSDL::isWindowVisible() const
|
||||
{
|
||||
return !IsInBackground;
|
||||
}
|
||||
|
||||
//! returns if window is active. if not, nothing need to be drawn
|
||||
bool CIrrDeviceSDL::isWindowActive() const
|
||||
{
|
||||
|
@ -1109,6 +1206,8 @@ void CIrrDeviceSDL::createKeyMap()
|
|||
|
||||
// buttons missing
|
||||
|
||||
KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_CANCEL));
|
||||
|
||||
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
|
||||
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
|
||||
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));
|
||||
|
|
|
@ -86,6 +86,9 @@ public:
|
|||
/** \return True if window is fullscreen. */
|
||||
bool isFullscreen() const override;
|
||||
|
||||
//! Checks if the window could possibly be visible.
|
||||
bool isWindowVisible() const override;
|
||||
|
||||
//! Get the position of this window on screen
|
||||
core::position2di getWindowPosition() override;
|
||||
|
||||
|
@ -277,13 +280,13 @@ private:
|
|||
void createDriver();
|
||||
|
||||
bool createWindow();
|
||||
bool createWindowWithContext();
|
||||
|
||||
void createKeyMap();
|
||||
|
||||
void logAttributes();
|
||||
SDL_GLContext Context;
|
||||
SDL_Window *Window;
|
||||
int SDL_Flags;
|
||||
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
|
||||
core::array<SDL_Joystick *> Joysticks;
|
||||
#endif
|
||||
|
@ -319,6 +322,7 @@ private:
|
|||
SDL_SysWMinfo Info;
|
||||
|
||||
s32 CurrentTouchCount;
|
||||
bool IsInBackground;
|
||||
};
|
||||
|
||||
} // end namespace irr
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
if(NOT ANDROID AND NOT APPLE)
|
||||
if(NOT APPLE)
|
||||
set(DEFAULT_SDL2 ON)
|
||||
endif()
|
||||
|
||||
|
@ -77,10 +77,9 @@ elseif(APPLE)
|
|||
set(DEVICE "OSX")
|
||||
elseif(ANDROID)
|
||||
add_definitions(-D_IRR_ANDROID_PLATFORM_)
|
||||
if(USE_SDL2)
|
||||
message(FATAL_ERROR "SDL2 device is not (yet) supported on Android")
|
||||
if(NOT USE_SDL2)
|
||||
message(FATAL_ERROR "The Android build requires SDL2")
|
||||
endif()
|
||||
set(DEVICE "ANDROID")
|
||||
elseif(EMSCRIPTEN)
|
||||
add_definitions(-D_IRR_EMSCRIPTEN_PLATFORM_ -D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||
set(LINUX_PLATFORM TRUE)
|
||||
|
@ -131,7 +130,10 @@ endif()
|
|||
# OpenGL
|
||||
|
||||
if(USE_SDL2)
|
||||
option(ENABLE_OPENGL3 "Enable OpenGL 3+" TRUE)
|
||||
if(NOT ANDROID)
|
||||
set(DEFAULT_OPENGL3 TRUE)
|
||||
endif()
|
||||
option(ENABLE_OPENGL3 "Enable OpenGL 3+" ${DEFAULT_OPENGL3})
|
||||
else()
|
||||
set(ENABLE_OPENGL3 FALSE)
|
||||
endif()
|
||||
|
@ -142,13 +144,10 @@ else()
|
|||
option(ENABLE_OPENGL "Enable OpenGL" TRUE)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN OR APPLE)
|
||||
if(USE_SDL2 OR EMSCRIPTEN OR APPLE)
|
||||
set(ENABLE_GLES1 FALSE)
|
||||
else()
|
||||
if(ANDROID)
|
||||
set(DEFAULT_GLES1 TRUE)
|
||||
endif()
|
||||
option(ENABLE_GLES1 "Enable OpenGL ES" ${DEFAULT_GLES1})
|
||||
option(ENABLE_GLES1 "Enable OpenGL ES" FALSE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
|
@ -188,19 +187,16 @@ if(ENABLE_OPENGL3)
|
|||
endif()
|
||||
|
||||
if(ENABLE_GLES1)
|
||||
if (USE_SDL2)
|
||||
message(FATAL_ERROR "OpenGL ES 1 is not supported with SDL2")
|
||||
endif()
|
||||
add_definitions(-D_IRR_COMPILE_WITH_OGLES1_)
|
||||
set(OPENGLES_DIRECT_LINK TRUE)
|
||||
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$")
|
||||
if(DEVICE MATCHES "^(WINDOWS|X11)$")
|
||||
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_GLES2)
|
||||
add_definitions(-D_IRR_COMPILE_WITH_OGLES2_)
|
||||
if(DEVICE MATCHES "^(WINDOWS|X11|ANDROID)$" OR EMSCRIPTEN)
|
||||
if(DEVICE MATCHES "^(WINDOWS|X11)$" OR EMSCRIPTEN)
|
||||
add_definitions(-D_IRR_COMPILE_WITH_EGL_MANAGER_)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -248,11 +244,15 @@ endif()
|
|||
if(ENABLE_GLES2)
|
||||
find_package(OpenGLES2 REQUIRED)
|
||||
endif()
|
||||
if(ENABLE_OPENGL OR ENABLE_OPENGL3)
|
||||
if(ENABLE_OPENGL)
|
||||
find_package(OpenGL REQUIRED)
|
||||
endif()
|
||||
if(USE_SDL2)
|
||||
find_package(SDL2 REQUIRED)
|
||||
if(NOT ANDROID)
|
||||
find_package(SDL2 REQUIRED)
|
||||
else()
|
||||
# provided by MinetestAndroidLibs.cmake
|
||||
endif()
|
||||
message(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
|
||||
|
||||
# unfortunately older SDL does not provide its version to cmake, so check header.
|
||||
|
@ -323,7 +323,6 @@ set(link_includes
|
|||
${OPENGLES2_INCLUDE_DIR}
|
||||
${EGL_INCLUDE_DIR}
|
||||
|
||||
"$<$<PLATFORM_ID:Android>:${ANDROID_NDK}/sources/android/native_app_glue>"
|
||||
"$<$<BOOL:${USE_X11}>:${X11_INCLUDE_DIR}>"
|
||||
)
|
||||
|
||||
|
@ -453,14 +452,7 @@ if(ENABLE_OPENGL3)
|
|||
target_compile_definitions(IRROTHEROBJ PRIVATE ENABLE_OPENGL3)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(IRROTHEROBJ PRIVATE
|
||||
Android/CIrrDeviceAndroid.cpp
|
||||
Android/CAndroidAssetReader.cpp
|
||||
Android/CAndroidAssetFileArchive.cpp
|
||||
Android/CKeyEventWrapper.cpp
|
||||
)
|
||||
elseif(APPLE)
|
||||
if(APPLE)
|
||||
# Build all IRROTHEROBJ sources as objc++, including the .cpp's
|
||||
set_target_properties(IRROTHEROBJ PROPERTIES COMPILE_OPTIONS "-xobjective-c++")
|
||||
target_sources(IRROTHEROBJ PRIVATE
|
||||
|
@ -535,7 +527,8 @@ target_link_libraries(IrrlichtMt PRIVATE
|
|||
"$<$<BOOL:${OPENGLES_DIRECT_LINK}>:${OPENGLES_LIBRARY}>"
|
||||
${EGL_LIBRARY}
|
||||
|
||||
"$<$<PLATFORM_ID:Android>:-landroid -llog>"
|
||||
# incl. transitive SDL2 dependencies for static linking
|
||||
"$<$<PLATFORM_ID:Android>:-landroid -llog -lGLESv2 -lGLESv1_CM -lOpenSLES>"
|
||||
${COCOA_LIB}
|
||||
${IOKIT_LIB}
|
||||
"$<$<PLATFORM_ID:Windows>:gdi32>"
|
||||
|
|
|
@ -983,6 +983,11 @@ IImage *CNullDriver::createImageFromFile(io::IReadFile *file)
|
|||
continue;
|
||||
|
||||
file->seek(0); // reset file position which might have changed due to previous loadImage calls
|
||||
// avoid warnings if extension is wrong
|
||||
if (!SurfaceLoader[i]->isALoadableFileFormat(file))
|
||||
continue;
|
||||
|
||||
file->seek(0);
|
||||
if (IImage *image = SurfaceLoader[i]->loadImage(file))
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -19,10 +19,6 @@
|
|||
#include "CImage.h"
|
||||
#include "os.h"
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
||||
#include "android_native_app_glue.h"
|
||||
#endif
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace video
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "SMaterial.h"
|
||||
#include "fast_atof.h"
|
||||
|
||||
#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_) || defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
|
||||
#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_)
|
||||
#include <EGL/egl.h>
|
||||
#else
|
||||
#include <GLES/egl.h>
|
||||
|
|
|
@ -28,10 +28,6 @@ static const char *const copyright = "Irrlicht Engine (c) 2002-2017 Nikolaus Geb
|
|||
#include "CIrrDeviceSDL.h"
|
||||
#endif
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
||||
#include "Android/CIrrDeviceAndroid.h"
|
||||
#endif
|
||||
|
||||
namespace irr
|
||||
{
|
||||
//! stub for calling createDeviceEx
|
||||
|
@ -74,11 +70,6 @@ extern "C" IRRLICHT_API IrrlichtDevice *IRRCALLCONV createDeviceEx(const SIrrlic
|
|||
dev = new CIrrDeviceLinux(params);
|
||||
#endif
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
||||
if (params.DeviceType == EIDT_ANDROID || (!dev && params.DeviceType == EIDT_BEST))
|
||||
dev = new CIrrDeviceAndroid(params);
|
||||
#endif
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
||||
if (params.DeviceType == EIDT_SDL || (!dev && params.DeviceType == EIDT_BEST))
|
||||
dev = new CIrrDeviceSDL(params);
|
||||
|
|
|
@ -21,10 +21,6 @@
|
|||
#include "CImage.h"
|
||||
#include "os.h"
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_
|
||||
#include "android_native_app_glue.h"
|
||||
#endif
|
||||
|
||||
#include "mt_opengl.h"
|
||||
|
||||
namespace irr
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
#include <commctrl.h>
|
||||
#include <richedit.h>
|
||||
|
||||
#ifndef USE_CMAKE_CONFIG_H
|
||||
#define USE_CMAKE_CONFIG_H
|
||||
#endif
|
||||
#include "config.h"
|
||||
#undef USE_CMAKE_CONFIG_H
|
||||
|
||||
#if RUN_IN_PLACE
|
||||
#define BUILDMODE "RUN_IN_PLACE=1"
|
||||
#else
|
||||
#define BUILDMODE "RUN_IN_PLACE=0"
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
|
||||
#endif
|
||||
|
||||
LANGUAGE 0, SUBLANG_NEUTRAL
|
||||
130 ICON "minetest-icon.ico"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifndef NDEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", ""
|
||||
VALUE "CompanyName", PROJECT_NAME_C " community"
|
||||
VALUE "FileDescription", PROJECT_NAME_C " engine"
|
||||
VALUE "FileVersion", VERSION_STRING
|
||||
VALUE "InternalName", PROJECT_NAME
|
||||
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
|
||||
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
|
||||
VALUE "OriginalFilename", "minetest.exe"
|
||||
VALUE "PrivateBuild", VERSION_EXTRA
|
||||
VALUE "ProductName", PROJECT_NAME_C
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
VALUE "SpecialBuild", BUILDMODE
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
#include <commctrl.h>
|
||||
#include <richedit.h>
|
||||
|
||||
#ifndef USE_CMAKE_CONFIG_H
|
||||
#define USE_CMAKE_CONFIG_H
|
||||
#endif
|
||||
#include "config.h"
|
||||
#undef USE_CMAKE_CONFIG_H
|
||||
|
||||
#if RUN_IN_PLACE
|
||||
#define BUILDMODE "RUN_IN_PLACE=1"
|
||||
#else
|
||||
#define BUILDMODE "RUN_IN_PLACE=0"
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "minetest.exe.manifest"
|
||||
#endif
|
||||
|
||||
LANGUAGE 0, SUBLANG_NEUTRAL
|
||||
130 ICON "minetest-icon.ico"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifndef NDEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", ""
|
||||
VALUE "CompanyName", PROJECT_NAME_C " community"
|
||||
VALUE "FileDescription", PROJECT_NAME_C " engine"
|
||||
VALUE "FileVersion", VERSION_STRING
|
||||
VALUE "InternalName", PROJECT_NAME
|
||||
VALUE "LegalCopyright", "(c) 2011-2015 celeron55"
|
||||
VALUE "LegalTrademarks", """Minetest"" is the property of the Minetest community, don't use it without permission!"
|
||||
VALUE "OriginalFilename", "minetest.exe"
|
||||
VALUE "PrivateBuild", VERSION_EXTRA
|
||||
VALUE "ProductName", PROJECT_NAME_C
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
VALUE "SpecialBuild", BUILDMODE
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
|
|
|
@ -307,10 +307,6 @@ else()
|
|||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
|
||||
add_library(native_app_glue OBJECT ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
||||
set(PLATFORM_LIBS ${PLATFORM_LIBS} native_app_glue)
|
||||
|
||||
set(PLATFORM_LIBS ${PLATFORM_LIBS} android log)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -518,6 +514,9 @@ include_directories(SYSTEM
|
|||
${GMP_INCLUDE_DIR}
|
||||
${JSON_INCLUDE_DIR}
|
||||
${LUA_BIT_INCLUDE_DIR}
|
||||
# on Android, Minetest depends on SDL2 directly
|
||||
# on other platforms, only IrrlichtMt depends on SDL2
|
||||
"$<$<PLATFORM_ID:Android>:${SDL2_INCLUDE_DIRS}>"
|
||||
)
|
||||
|
||||
if(USE_GETTEXT)
|
||||
|
@ -562,6 +561,9 @@ if(BUILD_CLIENT)
|
|||
${LUA_BIT_LIBRARY}
|
||||
${FREETYPE_LIBRARY}
|
||||
${PLATFORM_LIBS}
|
||||
# on Android, Minetest depends on SDL2 directly
|
||||
# on other platforms, only IrrlichtMt depends on SDL2
|
||||
"$<$<PLATFORM_ID:Android>:${SDL2_LIBRARIES}>"
|
||||
)
|
||||
if(NOT USE_LUAJIT)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
|
@ -698,11 +700,13 @@ include(CheckCSourceCompiles)
|
|||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR})
|
||||
if(USE_LUAJIT)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${LUA_LIBRARY})
|
||||
# libm usually required if statically linking
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${LUA_LIBRARY} m)
|
||||
# LuaJIT provides exactly zero ways to determine how recent it is (the version
|
||||
# is unchanged since 2017), however it happens that string buffers were added
|
||||
# after the changes which we care about so that works as an indicator.
|
||||
# (https://github.com/LuaJIT/LuaJIT/commit/4c6b669 March 2021)
|
||||
# Note: This is no longer true as of August 2023, but we're keeping the old check.
|
||||
unset(HAVE_RECENT_LJ CACHE)
|
||||
check_symbol_exists(luaopen_string_buffer "lualib.h" HAVE_RECENT_LJ)
|
||||
if(NOT HAVE_RECENT_LJ)
|
||||
|
|
|
@ -69,6 +69,7 @@ set(client_SRCS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/texturepaths.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/texturesource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/imagesource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadows.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/shadows/dynamicshadowsrender.cpp
|
||||
|
|
|
@ -799,7 +799,7 @@ bool Client::loadMedia(const std::string &data, const std::string &filename,
|
|||
video::IVideoDriver *vdrv = m_rendering_engine->get_video_driver();
|
||||
|
||||
io::IReadFile *rfile = irrfs->createMemoryReadFile(
|
||||
data.c_str(), data.size(), "_tempreadfile");
|
||||
data.c_str(), data.size(), filename.c_str());
|
||||
|
||||
FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
|
||||
|
||||
|
@ -1795,6 +1795,11 @@ float Client::mediaReceiveProgress()
|
|||
return 1.0; // downloader only exists when not yet done
|
||||
}
|
||||
|
||||
void Client::drawLoadScreen(const std::wstring &text, float dtime, int percent) {
|
||||
m_rendering_engine->run();
|
||||
m_rendering_engine->draw_load_screen(text, guienv, m_tsrc, dtime, percent);
|
||||
}
|
||||
|
||||
struct TextureUpdateArgs {
|
||||
gui::IGUIEnvironment *guienv;
|
||||
u64 last_time_ms;
|
||||
|
|
|
@ -356,6 +356,7 @@ public:
|
|||
|
||||
float mediaReceiveProgress();
|
||||
|
||||
void drawLoadScreen(const std::wstring &text, float dtime, int percent);
|
||||
void afterContentReceived();
|
||||
void showUpdateProgressTexture(void *args, u32 progress, u32 max_progress);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
*/
|
||||
|
||||
#include "clientmedia.h"
|
||||
#include "gettext.h"
|
||||
#include "httpfetch.h"
|
||||
#include "client.h"
|
||||
#include "filecache.h"
|
||||
|
@ -184,6 +185,11 @@ void ClientMediaDownloader::step(Client *client)
|
|||
|
||||
void ClientMediaDownloader::initialStep(Client *client)
|
||||
{
|
||||
std::wstring loading_text = wstrgettext("Media...");
|
||||
// Tradeoff between responsiveness during media loading and media loading speed
|
||||
const u64 chunk_time_ms = 33;
|
||||
u64 last_time = porting::getTimeMs();
|
||||
|
||||
// Check media cache
|
||||
m_uncached_count = m_files.size();
|
||||
for (auto &file_it : m_files) {
|
||||
|
@ -195,6 +201,13 @@ void ClientMediaDownloader::initialStep(Client *client)
|
|||
filestatus->received = true;
|
||||
m_uncached_count--;
|
||||
}
|
||||
|
||||
u64 cur_time = porting::getTimeMs();
|
||||
u64 dtime = porting::getDeltaMs(last_time, cur_time);
|
||||
if (dtime >= chunk_time_ms) {
|
||||
client->drawLoadScreen(loading_text, dtime / 1000.0f, 30);
|
||||
last_time = cur_time;
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_uncached_received_count == 0);
|
||||
|
|
|
@ -66,6 +66,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "settings.h"
|
||||
#include "shader.h"
|
||||
#include "sky.h"
|
||||
#include "threading/lambda.h"
|
||||
#include "translation.h"
|
||||
#include "util/basic_macros.h"
|
||||
#include "util/directiontables.h"
|
||||
|
@ -386,7 +387,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
|
|||
CachedVertexShaderSetting<float, 3> m_eye_position_vertex{"eyePosition"};
|
||||
CachedPixelShaderSetting<float, 3> m_minimap_yaw{"yawVec"};
|
||||
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel{"cameraOffset"};
|
||||
CachedPixelShaderSetting<float, 3> m_camera_offset_vertex{"cameraOffset"};
|
||||
CachedVertexShaderSetting<float, 3> m_camera_offset_vertex{"cameraOffset"};
|
||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
|
||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
|
||||
CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
|
||||
|
@ -484,10 +485,6 @@ public:
|
|||
m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services);
|
||||
m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services);
|
||||
|
||||
v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
|
||||
m_eye_position_pixel.set(epos, services);
|
||||
m_eye_position_vertex.set(epos, services);
|
||||
|
||||
if (m_client->getMinimap()) {
|
||||
v3f minimap_yaw = m_client->getMinimap()->getYawVec();
|
||||
m_minimap_yaw.set(minimap_yaw, services);
|
||||
|
@ -945,6 +942,7 @@ private:
|
|||
f32 m_cache_mouse_sensitivity;
|
||||
f32 m_cache_joystick_frustum_sensitivity;
|
||||
f32 m_repeat_place_time;
|
||||
f32 m_repeat_dig_time;
|
||||
f32 m_cache_cam_smoothing;
|
||||
|
||||
bool m_invert_mouse;
|
||||
|
@ -991,6 +989,8 @@ Game::Game() :
|
|||
&settingChangedCallback, this);
|
||||
g_settings->registerChangedCallback("repeat_place_time",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->registerChangedCallback("repeat_dig_time",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->registerChangedCallback("noclip",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->registerChangedCallback("free_move",
|
||||
|
@ -1023,12 +1023,6 @@ Game::Game() :
|
|||
|
||||
Game::~Game()
|
||||
{
|
||||
delete client;
|
||||
delete soundmaker;
|
||||
sound_manager.reset();
|
||||
|
||||
delete server; // deleted first to stop all server threads
|
||||
|
||||
delete hud;
|
||||
delete camera;
|
||||
delete quicktune;
|
||||
|
@ -1057,6 +1051,8 @@ Game::~Game()
|
|||
&settingChangedCallback, this);
|
||||
g_settings->deregisterChangedCallback("repeat_place_time",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->deregisterChangedCallback("repeat_dig_time",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->deregisterChangedCallback("noclip",
|
||||
&settingChangedCallback, this);
|
||||
g_settings->deregisterChangedCallback("free_move",
|
||||
|
@ -1275,6 +1271,28 @@ void Game::shutdown()
|
|||
sleep_ms(100);
|
||||
}
|
||||
}
|
||||
|
||||
delete client;
|
||||
delete soundmaker;
|
||||
sound_manager.reset();
|
||||
|
||||
auto stop_thread = runInThread([=] {
|
||||
delete server;
|
||||
}, "ServerStop");
|
||||
|
||||
FpsControl fps_control;
|
||||
fps_control.reset();
|
||||
|
||||
while (stop_thread->isRunning()) {
|
||||
m_rendering_engine->run();
|
||||
f32 dtime;
|
||||
fps_control.limit(device, &dtime);
|
||||
showOverlayMessage(N_("Shutting down..."), dtime, 0, false);
|
||||
}
|
||||
|
||||
stop_thread->rethrow();
|
||||
|
||||
// to be continued in Game::~Game
|
||||
}
|
||||
|
||||
|
||||
|
@ -1380,11 +1398,33 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
|
|||
|
||||
server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
|
||||
false, nullptr, error_message);
|
||||
server->start();
|
||||
|
||||
copyServerClientCache();
|
||||
auto start_thread = runInThread([=] {
|
||||
server->start();
|
||||
copyServerClientCache();
|
||||
}, "ServerStart");
|
||||
|
||||
return true;
|
||||
input->clear();
|
||||
bool success = true;
|
||||
|
||||
FpsControl fps_control;
|
||||
fps_control.reset();
|
||||
|
||||
while (start_thread->isRunning()) {
|
||||
if (!m_rendering_engine->run() || input->cancelPressed())
|
||||
success = false;
|
||||
f32 dtime;
|
||||
fps_control.limit(device, &dtime);
|
||||
|
||||
if (success)
|
||||
showOverlayMessage(N_("Creating server..."), dtime, 5);
|
||||
else
|
||||
showOverlayMessage(N_("Shutting down..."), dtime, 0, false);
|
||||
}
|
||||
|
||||
start_thread->rethrow();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void Game::copyServerClientCache()
|
||||
|
@ -2226,9 +2266,11 @@ void Game::openConsole(float scale, const wchar_t *line)
|
|||
assert(scale > 0.0f && scale <= 1.0f);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
porting::showTextInputDialog("", "", 2);
|
||||
m_android_chat_open = true;
|
||||
#else
|
||||
if (!porting::hasPhysicalKeyboardAndroid()) {
|
||||
porting::showTextInputDialog("", "", 2);
|
||||
m_android_chat_open = true;
|
||||
} else {
|
||||
#endif
|
||||
if (gui_chat_console->isOpenInhibited())
|
||||
return;
|
||||
gui_chat_console->openConsole(scale);
|
||||
|
@ -2236,6 +2278,8 @@ void Game::openConsole(float scale, const wchar_t *line)
|
|||
gui_chat_console->setCloseOnEnter(true);
|
||||
gui_chat_console->replaceAndAddToHistory(line);
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
} // else
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -3282,8 +3326,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
|
|||
if (pointed != runData.pointed_old)
|
||||
infostream << "Pointing at " << pointed.dump() << std::endl;
|
||||
|
||||
if (g_touchscreengui)
|
||||
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
|
||||
if (g_touchscreengui) {
|
||||
auto mode = selected_def.touch_interaction.getMode(pointed.type);
|
||||
g_touchscreengui->applyContextControls(mode);
|
||||
}
|
||||
|
||||
// Note that updating the selection mesh every frame is not particularly efficient,
|
||||
// but the halo rendering code is already inefficient so there's no point in optimizing it here
|
||||
|
@ -3919,12 +3965,14 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
|
|||
runData.nodig_delay_timer =
|
||||
runData.dig_time_complete / (float)crack_animation_length;
|
||||
|
||||
// We don't want a corresponding delay to very time consuming nodes
|
||||
// and nodes without digging time (e.g. torches) get a fixed delay.
|
||||
if (runData.nodig_delay_timer > 0.3)
|
||||
runData.nodig_delay_timer = 0.3;
|
||||
else if (runData.dig_instantly)
|
||||
runData.nodig_delay_timer = 0.15;
|
||||
// Don't add a corresponding delay to very time consuming nodes.
|
||||
runData.nodig_delay_timer = std::min(runData.nodig_delay_timer, 0.3f);
|
||||
|
||||
// Ensure that the delay between breaking nodes
|
||||
// (dig_time_complete + nodig_delay_timer) is at least the
|
||||
// value of the repeat_dig_time setting.
|
||||
runData.nodig_delay_timer = std::max(runData.nodig_delay_timer,
|
||||
m_repeat_dig_time - runData.dig_time_complete);
|
||||
|
||||
if (client->modsLoaded() &&
|
||||
client->getScript()->on_dignode(nodepos, n)) {
|
||||
|
@ -4323,7 +4371,8 @@ void Game::readSettings()
|
|||
m_cache_enable_fog = g_settings->getBool("enable_fog");
|
||||
m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
|
||||
m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
|
||||
m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.16f, 2.0);
|
||||
m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.15f, 2.0f);
|
||||
m_repeat_dig_time = g_settings->getFloat("repeat_dig_time", 0.15f, 2.0f);
|
||||
|
||||
m_cache_enable_noclip = g_settings->getBool("noclip");
|
||||
m_cache_enable_free_move = g_settings->getBool("free_move");
|
||||
|
|
|
@ -44,6 +44,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#define OBJECT_CROSSHAIR_LINE_SIZE 8
|
||||
#define CROSSHAIR_LINE_SIZE 10
|
||||
|
||||
static void setting_changed_callback(const std::string &name, void *data)
|
||||
{
|
||||
static_cast<Hud*>(data)->readScalingSetting();
|
||||
}
|
||||
|
||||
Hud::Hud(Client *client, LocalPlayer *player,
|
||||
Inventory *inventory)
|
||||
{
|
||||
|
@ -52,12 +57,8 @@ Hud::Hud(Client *client, LocalPlayer *player,
|
|||
this->player = player;
|
||||
this->inventory = inventory;
|
||||
|
||||
m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
|
||||
m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
|
||||
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
|
||||
RenderingEngine::getDisplayDensity() + 0.5f);
|
||||
m_hotbar_imagesize *= m_hud_scaling;
|
||||
m_padding = m_hotbar_imagesize / 12;
|
||||
readScalingSetting();
|
||||
g_settings->registerChangedCallback("hud_scaling", setting_changed_callback, this);
|
||||
|
||||
for (auto &hbar_color : hbar_colors)
|
||||
hbar_color = video::SColor(255, 255, 255, 255);
|
||||
|
@ -138,8 +139,20 @@ Hud::Hud(Client *client, LocalPlayer *player,
|
|||
m_rotation_mesh_buffer.setHardwareMappingHint(scene::EHM_STATIC);
|
||||
}
|
||||
|
||||
void Hud::readScalingSetting()
|
||||
{
|
||||
m_hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
|
||||
m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
|
||||
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
|
||||
RenderingEngine::getDisplayDensity() + 0.5f);
|
||||
m_hotbar_imagesize *= m_hud_scaling;
|
||||
m_padding = m_hotbar_imagesize / 12;
|
||||
}
|
||||
|
||||
Hud::~Hud()
|
||||
{
|
||||
g_settings->deregisterChangedCallback("hud_scaling", setting_changed_callback, this);
|
||||
|
||||
if (m_selection_mesh)
|
||||
m_selection_mesh->drop();
|
||||
}
|
||||
|
@ -786,9 +799,9 @@ void Hud::drawCrosshair()
|
|||
{
|
||||
auto draw_image_crosshair = [this] (video::ITexture *tex) {
|
||||
core::dimension2di orig_size(tex->getOriginalSize());
|
||||
core::dimension2di scaled_size(
|
||||
core::round32(orig_size.Width * m_scale_factor),
|
||||
core::round32(orig_size.Height * m_scale_factor));
|
||||
// Integer scaling to avoid artifacts, floor instead of round since too
|
||||
// small looks better than too large in this case.
|
||||
core::dimension2di scaled_size = orig_size * std::max(std::floor(m_scale_factor), 1.0f);
|
||||
|
||||
core::rect<s32> src_rect(orig_size);
|
||||
core::position2d pos(m_displaycenter.X - scaled_size.Width / 2,
|
||||
|
|
|
@ -57,6 +57,7 @@ public:
|
|||
|
||||
Hud(Client *client, LocalPlayer *player,
|
||||
Inventory *inventory);
|
||||
void readScalingSetting();
|
||||
~Hud();
|
||||
|
||||
enum BlockBoundsMode toggleBlockBounds();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IImage.h>
|
||||
#include <string>
|
||||
#include "settings.h"
|
||||
|
||||
// This file is only used for internal generation of images.
|
||||
// Use texturesource.h to handle textures.
|
||||
|
||||
// A cache used for storing source images.
|
||||
// (A "source image" is an unmodified image directly taken from the filesystem.)
|
||||
// Does not contain modified images.
|
||||
class SourceImageCache {
|
||||
public:
|
||||
~SourceImageCache();
|
||||
|
||||
void insert(const std::string &name, video::IImage *img, bool prefer_local);
|
||||
|
||||
video::IImage* get(const std::string &name);
|
||||
|
||||
// Primarily fetches from cache, secondarily tries to read from filesystem.
|
||||
video::IImage *getOrLoad(const std::string &name);
|
||||
private:
|
||||
std::map<std::string, video::IImage*> m_images;
|
||||
};
|
||||
|
||||
// Generates images using texture modifiers, and caches source images.
|
||||
struct ImageSource {
|
||||
/*! Generates an image from a full string like
|
||||
* "stone.png^mineral_coal.png^[crack:1:0".
|
||||
* The returned Image should be dropped.
|
||||
* source_image_names is important to determine when to flush the image from a cache (dynamic media)
|
||||
*/
|
||||
video::IImage* generateImage(std::string_view name, std::set<std::string> &source_image_names);
|
||||
|
||||
// Insert a source image into the cache without touching the filesystem.
|
||||
void insertSourceImage(const std::string &name, video::IImage *img, bool prefer_local);
|
||||
|
||||
// TODO should probably be moved elsewhere
|
||||
static video::SColor getImageAverageColor(const video::IImage &image);
|
||||
|
||||
ImageSource() :
|
||||
m_setting_mipmap{g_settings->getBool("mip_map")},
|
||||
m_setting_trilinear_filter{g_settings->getBool("trilinear_filter")},
|
||||
m_setting_bilinear_filter{g_settings->getBool("bilinear_filter")},
|
||||
m_setting_anisotropic_filter{g_settings->getBool("anisotropic_filter")}
|
||||
{};
|
||||
|
||||
private:
|
||||
|
||||
// Generate image based on a string like "stone.png" or "[crack:1:0".
|
||||
// If baseimg is NULL, it is created. Otherwise stuff is made on it.
|
||||
// source_image_names is important to determine when to flush the image from a cache (dynamic media).
|
||||
bool generateImagePart(std::string_view part_of_name, video::IImage *& baseimg,
|
||||
std::set<std::string> &source_image_names);
|
||||
|
||||
// Cached settings needed for making textures from meshes
|
||||
bool m_setting_mipmap;
|
||||
bool m_setting_trilinear_filter;
|
||||
bool m_setting_bilinear_filter;
|
||||
bool m_setting_anisotropic_filter;
|
||||
|
||||
// Cache of source images
|
||||
SourceImageCache m_sourcecache;
|
||||
};
|
|
@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
#include "particles.h"
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include "client.h"
|
||||
#include "collision.h"
|
||||
#include "client/content_cao.h"
|
||||
|
@ -26,21 +27,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "client/renderingengine.h"
|
||||
#include "util/numeric.h"
|
||||
#include "light.h"
|
||||
#include "localplayer.h"
|
||||
#include "environment.h"
|
||||
#include "clientmap.h"
|
||||
#include "mapnode.h"
|
||||
#include "nodedef.h"
|
||||
#include "client.h"
|
||||
#include "settings.h"
|
||||
#include "profiler.h"
|
||||
|
||||
ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc)
|
||||
{
|
||||
tex = p;
|
||||
// note: getTextureForMesh not needed here because we don't use texture filtering
|
||||
ref = tsrc->getTexture(p.string);
|
||||
}
|
||||
|
||||
/*
|
||||
Particle
|
||||
*/
|
||||
|
||||
Particle::Particle(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ClientEnvironment *env,
|
||||
const ParticleParameters &p,
|
||||
const ClientParticleTexRef &texture,
|
||||
v2f texpos,
|
||||
|
@ -49,14 +56,10 @@ Particle::Particle(
|
|||
ParticleSpawner *parent,
|
||||
std::unique_ptr<ClientParticleTexture> owned_texture
|
||||
) :
|
||||
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
|
||||
((Client *)gamedef)->getSceneManager()),
|
||||
|
||||
m_expiration(p.expirationtime),
|
||||
|
||||
m_env(env),
|
||||
m_gamedef(gamedef),
|
||||
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
|
||||
m_base_color(color),
|
||||
|
||||
m_texture(texture),
|
||||
m_texpos(texpos),
|
||||
m_texsize(texsize),
|
||||
|
@ -64,102 +67,30 @@ Particle::Particle(
|
|||
m_velocity(p.vel),
|
||||
m_acceleration(p.acc),
|
||||
m_p(p),
|
||||
m_player(player),
|
||||
|
||||
m_base_color(color),
|
||||
m_color(color),
|
||||
|
||||
m_parent(parent),
|
||||
m_owned_texture(std::move(owned_texture))
|
||||
{
|
||||
// Set material
|
||||
{
|
||||
// translate blend modes to GL blend functions
|
||||
video::E_BLEND_FACTOR bfsrc, bfdst;
|
||||
video::E_BLEND_OPERATION blendop;
|
||||
const auto blendmode = texture.tex != nullptr
|
||||
? texture.tex->blendmode
|
||||
: ParticleParamTypes::BlendMode::alpha;
|
||||
}
|
||||
|
||||
switch (blendmode) {
|
||||
case ParticleParamTypes::BlendMode::add:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
Particle::~Particle()
|
||||
{
|
||||
if (m_buffer)
|
||||
m_buffer->release(m_index);
|
||||
}
|
||||
|
||||
case ParticleParamTypes::BlendMode::sub:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_REVSUBTRACT;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::screen:
|
||||
bfsrc = video::EBF_ONE;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
default: // includes ParticleParamTypes::BlendMode::alpha
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
}
|
||||
|
||||
// Texture
|
||||
m_material.Lighting = false;
|
||||
m_material.BackfaceCulling = false;
|
||||
m_material.FogEnable = true;
|
||||
m_material.forEachTexture([] (auto &tex) {
|
||||
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
||||
tex.MagFilter = video::ETMAGF_NEAREST;
|
||||
});
|
||||
|
||||
// correctly render layered transparent particles -- see #10398
|
||||
m_material.ZWriteEnable = video::EZW_AUTO;
|
||||
|
||||
// enable alpha blending and set blend mode
|
||||
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
||||
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
|
||||
bfsrc, bfdst,
|
||||
video::EMFN_MODULATE_1X,
|
||||
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
||||
m_material.BlendOperation = blendop;
|
||||
m_material.setTexture(0, m_texture.ref);
|
||||
bool Particle::attachToBuffer(ParticleBuffer *buffer)
|
||||
{
|
||||
auto index_opt = buffer->allocate();
|
||||
if (index_opt.has_value()) {
|
||||
m_index = index_opt.value();
|
||||
m_buffer = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Irrlicht stuff
|
||||
this->setAutomaticCulling(scene::EAC_OFF);
|
||||
|
||||
// Init lighting
|
||||
updateLight();
|
||||
|
||||
// Init model
|
||||
updateVertices();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Particle::OnRegisterSceneNode()
|
||||
{
|
||||
if (IsVisible)
|
||||
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
||||
|
||||
ISceneNode::OnRegisterSceneNode();
|
||||
}
|
||||
|
||||
void Particle::render()
|
||||
{
|
||||
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
||||
driver->setMaterial(m_material);
|
||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||
|
||||
u16 indices[] = {0,1,2, 2,3,0};
|
||||
driver->drawVertexPrimitiveList(m_vertices, 4,
|
||||
indices, 2, video::EVT_STANDARD,
|
||||
scene::EPT_TRIANGLES, video::EIT_16BIT);
|
||||
}
|
||||
|
||||
void Particle::step(float dtime)
|
||||
void Particle::step(float dtime, ClientEnvironment *env)
|
||||
{
|
||||
m_time += dtime;
|
||||
|
||||
|
@ -169,10 +100,10 @@ void Particle::step(float dtime)
|
|||
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
|
||||
|
||||
if (m_p.collisiondetection) {
|
||||
aabb3f box = m_collisionbox;
|
||||
aabb3f box(v3f(-m_p.size / 2.0f), v3f(m_p.size / 2.0f));
|
||||
v3f p_pos = m_pos * BS;
|
||||
v3f p_velocity = m_velocity * BS;
|
||||
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
|
||||
collisionMoveResult r = collisionMoveSimple(env, env->getGameDef(), BS * 0.5f,
|
||||
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
|
||||
m_p.object_collision);
|
||||
|
||||
|
@ -215,7 +146,7 @@ void Particle::step(float dtime)
|
|||
m_animation_time += dtime;
|
||||
int frame_length_i = 0;
|
||||
m_p.animation.determineParams(
|
||||
m_material.getTexture(0)->getSize(),
|
||||
m_texture.ref->getSize(),
|
||||
NULL, &frame_length_i, NULL);
|
||||
float frame_length = frame_length_i / 1000.0;
|
||||
while (m_animation_time > frame_length) {
|
||||
|
@ -225,23 +156,19 @@ void Particle::step(float dtime)
|
|||
}
|
||||
|
||||
// animate particle alpha in accordance with settings
|
||||
float alpha = 1.f;
|
||||
if (m_texture.tex != nullptr)
|
||||
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
||||
else
|
||||
m_alpha = 1.f;
|
||||
alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
||||
|
||||
// Update lighting
|
||||
updateLight();
|
||||
auto col = updateLight(env);
|
||||
col.setAlpha(255 * alpha);
|
||||
|
||||
// Update model
|
||||
updateVertices();
|
||||
|
||||
// Update position -- see #10398
|
||||
v3s16 camera_offset = m_env->getCameraOffset();
|
||||
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
|
||||
updateVertices(env, col);
|
||||
}
|
||||
|
||||
void Particle::updateLight()
|
||||
video::SColor Particle::updateLight(ClientEnvironment *env)
|
||||
{
|
||||
u8 light = 0;
|
||||
bool pos_ok;
|
||||
|
@ -251,32 +178,37 @@ void Particle::updateLight()
|
|||
floor(m_pos.Y+0.5),
|
||||
floor(m_pos.Z+0.5)
|
||||
);
|
||||
MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
|
||||
MapNode n = env->getClientMap().getNode(p, &pos_ok);
|
||||
if (pos_ok)
|
||||
light = n.getLightBlend(m_env->getDayNightRatio(),
|
||||
m_gamedef->ndef()->getLightingFlags(n));
|
||||
light = n.getLightBlend(env->getDayNightRatio(),
|
||||
env->getGameDef()->ndef()->getLightingFlags(n));
|
||||
else
|
||||
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
|
||||
light = blend_light(env->getDayNightRatio(), LIGHT_SUN, 0);
|
||||
|
||||
u8 m_light = decode_light(light + m_p.glow);
|
||||
m_color.set(m_alpha*255,
|
||||
return video::SColor(255,
|
||||
m_light * m_base_color.getRed() / 255,
|
||||
m_light * m_base_color.getGreen() / 255,
|
||||
m_light * m_base_color.getBlue() / 255);
|
||||
}
|
||||
|
||||
void Particle::updateVertices()
|
||||
void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
|
||||
{
|
||||
f32 tx0, tx1, ty0, ty1;
|
||||
v2f scale;
|
||||
|
||||
if (!m_buffer)
|
||||
return;
|
||||
|
||||
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
|
||||
|
||||
if (m_texture.tex != nullptr)
|
||||
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
|
||||
else
|
||||
scale = v2f(1.f, 1.f);
|
||||
|
||||
if (m_p.animation.type != TAT_NONE) {
|
||||
const v2u32 texsize = m_material.getTexture(0)->getSize();
|
||||
const v2u32 texsize = m_texture.ref->getSize();
|
||||
v2f texcoord, framesize_f;
|
||||
v2u32 framesize;
|
||||
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
|
||||
|
@ -297,31 +229,30 @@ void Particle::updateVertices()
|
|||
auto half = m_p.size * .5f,
|
||||
hx = half * scale.X,
|
||||
hy = half * scale.Y;
|
||||
m_vertices[0] = video::S3DVertex(-hx, -hy,
|
||||
0, 0, 0, 0, m_color, tx0, ty1);
|
||||
m_vertices[1] = video::S3DVertex(hx, -hy,
|
||||
0, 0, 0, 0, m_color, tx1, ty1);
|
||||
m_vertices[2] = video::S3DVertex(hx, hy,
|
||||
0, 0, 0, 0, m_color, tx1, ty0);
|
||||
m_vertices[3] = video::S3DVertex(-hx, hy,
|
||||
0, 0, 0, 0, m_color, tx0, ty0);
|
||||
vertices[0] = video::S3DVertex(-hx, -hy,
|
||||
0, 0, 0, 0, color, tx0, ty1);
|
||||
vertices[1] = video::S3DVertex(hx, -hy,
|
||||
0, 0, 0, 0, color, tx1, ty1);
|
||||
vertices[2] = video::S3DVertex(hx, hy,
|
||||
0, 0, 0, 0, color, tx1, ty0);
|
||||
vertices[3] = video::S3DVertex(-hx, hy,
|
||||
0, 0, 0, 0, color, tx0, ty0);
|
||||
|
||||
// Update position -- see #10398
|
||||
auto *player = env->getLocalPlayer();
|
||||
v3s16 camera_offset = env->getCameraOffset();
|
||||
|
||||
// see #10398
|
||||
// v3s16 camera_offset = m_env->getCameraOffset();
|
||||
// particle position is now handled by step()
|
||||
m_box.reset(v3f());
|
||||
|
||||
for (video::S3DVertex &vertex : m_vertices) {
|
||||
for (u16 i = 0; i < 4; i++) {
|
||||
video::S3DVertex &vertex = vertices[i];
|
||||
if (m_p.vertical) {
|
||||
v3f ppos = m_player->getPosition()/BS;
|
||||
v3f ppos = player->getPosition() / BS;
|
||||
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
|
||||
core::DEGTORAD + 90);
|
||||
} else {
|
||||
vertex.Pos.rotateYZBy(m_player->getPitch());
|
||||
vertex.Pos.rotateXZBy(m_player->getYaw());
|
||||
vertex.Pos.rotateYZBy(player->getPitch());
|
||||
vertex.Pos.rotateXZBy(player->getYaw());
|
||||
}
|
||||
m_box.addInternalPoint(vertex.Pos);
|
||||
vertex.Pos += m_pos * BS - intToFloat(camera_offset, BS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,7 +261,6 @@ void Particle::updateVertices()
|
|||
*/
|
||||
|
||||
ParticleSpawner::ParticleSpawner(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
const ParticleSpawnerParameters ¶ms,
|
||||
u16 attached_id,
|
||||
|
@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner(
|
|||
m_active(0),
|
||||
m_particlemanager(p_manager),
|
||||
m_time(0.0f),
|
||||
m_gamedef(gamedef),
|
||||
m_player(player),
|
||||
p(params),
|
||||
m_texpool(std::move(texpool)),
|
||||
|
@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
|
|||
|
||||
++m_active;
|
||||
m_particlemanager->addParticle(std::make_unique<Particle>(
|
||||
m_gamedef,
|
||||
m_player,
|
||||
env,
|
||||
pp,
|
||||
texture,
|
||||
texpos,
|
||||
|
@ -624,6 +550,109 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
ParticleBuffer
|
||||
*/
|
||||
|
||||
ParticleBuffer::ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material)
|
||||
: scene::ISceneNode(
|
||||
env->getGameDef()->getSceneManager()->getRootSceneNode(),
|
||||
env->getGameDef()->getSceneManager()),
|
||||
m_mesh_buffer(make_irr<scene::SMeshBuffer>())
|
||||
{
|
||||
m_mesh_buffer->getMaterial() = material;
|
||||
}
|
||||
|
||||
static constexpr u16 quad_indices[] = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
std::optional<u16> ParticleBuffer::allocate()
|
||||
{
|
||||
u16 index;
|
||||
|
||||
m_usage_timer = 0;
|
||||
|
||||
if (!m_free_list.empty()) {
|
||||
index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
auto *vertices = static_cast<video::S3DVertex*>(m_mesh_buffer->getVertices());
|
||||
u16 *indices = m_mesh_buffer->getIndices();
|
||||
// reset vertices, because it is only written in Particle::step()
|
||||
for (u16 i = 0; i < 4; i++)
|
||||
vertices[4 * index + i] = video::S3DVertex();
|
||||
for (u16 i = 0; i < 6; i++)
|
||||
indices[6 * index + i] = 4 * index + quad_indices[i];
|
||||
return index;
|
||||
}
|
||||
|
||||
if (m_count >= MAX_PARTICLES_PER_BUFFER)
|
||||
return std::nullopt;
|
||||
|
||||
// append new vertices
|
||||
// note: Our buffer never gets smaller, but ParticleManager will delete
|
||||
// us after a while.
|
||||
std::array<video::S3DVertex, 4> vertices {};
|
||||
m_mesh_buffer->append(&vertices.front(), 4, quad_indices, 6);
|
||||
index = m_count++;
|
||||
return index;
|
||||
}
|
||||
|
||||
void ParticleBuffer::release(u16 index)
|
||||
{
|
||||
assert(index < m_count);
|
||||
u16 *indices = m_mesh_buffer->getIndices();
|
||||
for (u16 i = 0; i < 6; i++)
|
||||
indices[6 * index + i] = 0;
|
||||
m_free_list.push_back(index);
|
||||
}
|
||||
|
||||
video::S3DVertex *ParticleBuffer::getVertices(u16 index)
|
||||
{
|
||||
if (index >= m_count)
|
||||
return nullptr;
|
||||
m_bounding_box_dirty = true;
|
||||
return &(static_cast<video::S3DVertex *>(m_mesh_buffer->getVertices())[4 * index]);
|
||||
}
|
||||
|
||||
void ParticleBuffer::OnRegisterSceneNode()
|
||||
{
|
||||
if (IsVisible)
|
||||
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
||||
scene::ISceneNode::OnRegisterSceneNode();
|
||||
}
|
||||
|
||||
const core::aabbox3df &ParticleBuffer::getBoundingBox() const
|
||||
{
|
||||
if (!m_bounding_box_dirty)
|
||||
return m_mesh_buffer->BoundingBox;
|
||||
|
||||
core::aabbox3df box;
|
||||
for (u16 i = 0; i < m_count; i++) {
|
||||
// check if this index is used
|
||||
static_assert(quad_indices[1] != 0);
|
||||
if (m_mesh_buffer->getIndices()[6 * i + 1] == 0)
|
||||
continue;
|
||||
|
||||
for (u16 j = 0; j < 4; j++)
|
||||
box.addInternalPoint(m_mesh_buffer->getPosition(i * 4 + j));
|
||||
}
|
||||
|
||||
m_mesh_buffer->BoundingBox = box;
|
||||
m_bounding_box_dirty = false;
|
||||
return m_mesh_buffer->BoundingBox;
|
||||
}
|
||||
|
||||
void ParticleBuffer::render()
|
||||
{
|
||||
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
||||
|
||||
if (isEmpty())
|
||||
return;
|
||||
|
||||
driver->setTransform(video::ETS_WORLD, core::matrix4());
|
||||
driver->setMaterial(m_mesh_buffer->getMaterial());
|
||||
driver->drawMeshBuffer(m_mesh_buffer.get());
|
||||
}
|
||||
|
||||
/*
|
||||
ParticleManager
|
||||
*/
|
||||
|
@ -639,8 +668,9 @@ ParticleManager::~ParticleManager()
|
|||
|
||||
void ParticleManager::step(float dtime)
|
||||
{
|
||||
stepParticles (dtime);
|
||||
stepSpawners (dtime);
|
||||
stepParticles(dtime);
|
||||
stepSpawners(dtime);
|
||||
stepBuffers(dtime);
|
||||
}
|
||||
|
||||
void ParticleManager::stepSpawners(float dtime)
|
||||
|
@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime)
|
|||
assert(parent->hasActive());
|
||||
parent->decrActive();
|
||||
}
|
||||
// remove scene node
|
||||
p.remove();
|
||||
// delete
|
||||
m_particles[i] = std::move(m_particles.back());
|
||||
m_particles.pop_back();
|
||||
} else {
|
||||
p.step(dtime);
|
||||
p.step(dtime, m_env);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleManager::stepBuffers(float dtime)
|
||||
{
|
||||
constexpr float INTERVAL = 0.5f;
|
||||
if (!m_buffer_gc.step(dtime, INTERVAL))
|
||||
return;
|
||||
|
||||
MutexAutoLock lock(m_particle_list_lock);
|
||||
|
||||
// remove buffers that have been unused for 5 seconds
|
||||
size_t alloc = 0;
|
||||
for (size_t i = 0; i < m_particle_buffers.size(); ) {
|
||||
auto &buf = m_particle_buffers[i];
|
||||
buf->m_usage_timer += INTERVAL;
|
||||
if (buf->isEmpty() && buf->m_usage_timer > 5.0f) {
|
||||
// delete and swap with last
|
||||
buf->remove();
|
||||
buf = std::move(m_particle_buffers.back());
|
||||
m_particle_buffers.pop_back();
|
||||
} else {
|
||||
i++;
|
||||
alloc += buf->m_count;
|
||||
}
|
||||
}
|
||||
|
||||
g_profiler->avg("ParticleManager: particle buffer count [#]", m_particle_buffers.size());
|
||||
if (!m_particle_buffers.empty())
|
||||
g_profiler->avg("ParticleManager: buffer allocated size [#]", alloc);
|
||||
}
|
||||
|
||||
void ParticleManager::clearAll()
|
||||
{
|
||||
MutexAutoLock lock(m_spawner_list_lock);
|
||||
MutexAutoLock lock2(m_particle_list_lock);
|
||||
|
||||
// clear particle spawners
|
||||
m_particle_spawners.clear();
|
||||
m_dying_particle_spawners.clear();
|
||||
|
||||
// clear particles
|
||||
for (std::unique_ptr<Particle> &p : m_particles) {
|
||||
// remove scene node
|
||||
p->remove();
|
||||
// delete
|
||||
p.reset();
|
||||
}
|
||||
m_particles.clear();
|
||||
|
||||
// have to remove from scene first because it keeps a reference
|
||||
for (auto &it : m_particle_buffers)
|
||||
it->remove();
|
||||
m_particle_buffers.clear();
|
||||
}
|
||||
|
||||
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||
|
@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
|||
|
||||
addParticleSpawner(event->add_particlespawner.id,
|
||||
std::make_unique<ParticleSpawner>(
|
||||
client,
|
||||
player,
|
||||
p,
|
||||
event->add_particlespawner.attached_id,
|
||||
|
@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
|||
p.size = oldsize;
|
||||
|
||||
if (texture.ref) {
|
||||
addParticle(std::make_unique<Particle>(client, player, m_env,
|
||||
addParticle(std::make_unique<Particle>(
|
||||
p, texture, texpos, texsize, color, nullptr,
|
||||
std::move(texstore)));
|
||||
}
|
||||
|
@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
|
|||
);
|
||||
|
||||
addParticle(std::make_unique<Particle>(
|
||||
gamedef,
|
||||
player,
|
||||
m_env,
|
||||
p,
|
||||
ClientParticleTexRef(ref),
|
||||
texpos,
|
||||
|
@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate)
|
|||
m_particles.reserve(m_particles.size() + max_estimate);
|
||||
}
|
||||
|
||||
void ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
||||
video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture)
|
||||
{
|
||||
// translate blend modes to GL blend functions
|
||||
video::E_BLEND_FACTOR bfsrc, bfdst;
|
||||
video::E_BLEND_OPERATION blendop;
|
||||
const auto blendmode = texture.tex ? texture.tex->blendmode :
|
||||
ParticleParamTypes::BlendMode::alpha;
|
||||
|
||||
switch (blendmode) {
|
||||
case ParticleParamTypes::BlendMode::add:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::sub:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_REVSUBTRACT;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::screen:
|
||||
bfsrc = video::EBF_ONE;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
default: // includes ParticleParamTypes::BlendMode::alpha
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
}
|
||||
|
||||
video::SMaterial material;
|
||||
|
||||
// Texture
|
||||
material.Lighting = false;
|
||||
material.BackfaceCulling = false;
|
||||
material.FogEnable = true;
|
||||
material.forEachTexture([] (auto &tex) {
|
||||
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
||||
tex.MagFilter = video::ETMAGF_NEAREST;
|
||||
});
|
||||
|
||||
// We don't have working transparency sorting. Disable Z-Write for
|
||||
// correct results for clipped-alpha at least.
|
||||
material.ZWriteEnable = video::EZW_OFF;
|
||||
|
||||
// enable alpha blending and set blend mode
|
||||
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
||||
material.MaterialTypeParam = video::pack_textureBlendFunc(
|
||||
bfsrc, bfdst,
|
||||
video::EMFN_MODULATE_1X,
|
||||
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
||||
material.BlendOperation = blendop;
|
||||
assert(texture.ref);
|
||||
material.setTexture(0, texture.ref);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
bool ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
||||
{
|
||||
MutexAutoLock lock(m_particle_list_lock);
|
||||
|
||||
m_particles.push_back(std::move(toadd));
|
||||
}
|
||||
auto material = getMaterialForParticle(toadd->getTextureRef());
|
||||
|
||||
ParticleBuffer *found = nullptr;
|
||||
// simple shortcut when multiple particles of the same type get added
|
||||
if (!m_particles.empty()) {
|
||||
auto &last = m_particles.back();
|
||||
if (last->getBuffer() && last->getBuffer()->getMaterial(0) == material)
|
||||
found = last->getBuffer();
|
||||
}
|
||||
// search fitting buffer
|
||||
if (!found) {
|
||||
for (auto &buffer : m_particle_buffers) {
|
||||
if (buffer->getMaterial(0) == material) {
|
||||
found = buffer.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// or create a new one
|
||||
if (!found) {
|
||||
auto tmp = make_irr<ParticleBuffer>(m_env, material);
|
||||
found = tmp.get();
|
||||
m_particle_buffers.push_back(std::move(tmp));
|
||||
}
|
||||
|
||||
if (!toadd->attachToBuffer(found)) {
|
||||
infostream << "ParticleManager: buffer full, dropping particle" << std::endl;
|
||||
return false;
|
||||
}
|
||||
m_particles.push_back(std::move(toadd));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
|
||||
{
|
||||
|
|
|
@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
#include "localplayer.h"
|
||||
#include "irr_ptr.h"
|
||||
#include "../particles.h"
|
||||
|
||||
struct ClientEvent;
|
||||
|
@ -29,6 +30,10 @@ class ParticleManager;
|
|||
class ClientEnvironment;
|
||||
struct MapNode;
|
||||
struct ContentFeatures;
|
||||
class LocalPlayer;
|
||||
class ITextureSource;
|
||||
class IGameDef;
|
||||
class Client;
|
||||
|
||||
struct ClientParticleTexture
|
||||
{
|
||||
|
@ -38,9 +43,7 @@ struct ClientParticleTexture
|
|||
video::ITexture *ref = nullptr;
|
||||
|
||||
ClientParticleTexture() = default;
|
||||
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t):
|
||||
tex(p),
|
||||
ref(t->getTextureForMesh(p.string)) {};
|
||||
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc);
|
||||
};
|
||||
|
||||
struct ClientParticleTexRef
|
||||
|
@ -61,14 +64,12 @@ struct ClientParticleTexRef
|
|||
};
|
||||
|
||||
class ParticleSpawner;
|
||||
class ParticleBuffer;
|
||||
|
||||
class Particle : public scene::ISceneNode
|
||||
class Particle
|
||||
{
|
||||
public:
|
||||
Particle(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ClientEnvironment *env,
|
||||
const ParticleParameters &p,
|
||||
const ClientParticleTexRef &texture,
|
||||
v2f texpos,
|
||||
|
@ -78,61 +79,46 @@ public:
|
|||
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
|
||||
);
|
||||
|
||||
virtual const aabb3f &getBoundingBox() const
|
||||
{
|
||||
return m_box;
|
||||
}
|
||||
~Particle();
|
||||
|
||||
virtual u32 getMaterialCount() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
DISABLE_CLASS_COPY(Particle)
|
||||
|
||||
virtual video::SMaterial& getMaterial(u32 i)
|
||||
{
|
||||
return m_material;
|
||||
}
|
||||
void step(float dtime, ClientEnvironment *env);
|
||||
|
||||
virtual void OnRegisterSceneNode();
|
||||
virtual void render();
|
||||
|
||||
void step(float dtime);
|
||||
|
||||
bool isExpired ()
|
||||
bool isExpired () const
|
||||
{ return m_expiration < m_time; }
|
||||
|
||||
ParticleSpawner *getParent() { return m_parent; }
|
||||
ParticleSpawner *getParent() const { return m_parent; }
|
||||
|
||||
const ClientParticleTexRef &getTextureRef() const { return m_texture; }
|
||||
|
||||
ParticleBuffer *getBuffer() const { return m_buffer; }
|
||||
bool attachToBuffer(ParticleBuffer *buffer);
|
||||
|
||||
private:
|
||||
void updateLight();
|
||||
void updateVertices();
|
||||
void setVertexAlpha(float a);
|
||||
video::SColor updateLight(ClientEnvironment *env);
|
||||
void updateVertices(ClientEnvironment *env, video::SColor color);
|
||||
|
||||
ParticleBuffer *m_buffer = nullptr;
|
||||
u16 m_index; // index in m_buffer
|
||||
|
||||
video::S3DVertex m_vertices[4];
|
||||
float m_time = 0.0f;
|
||||
float m_expiration;
|
||||
|
||||
ClientEnvironment *m_env;
|
||||
IGameDef *m_gamedef;
|
||||
aabb3f m_box;
|
||||
aabb3f m_collisionbox;
|
||||
// Color without lighting
|
||||
video::SColor m_base_color;
|
||||
|
||||
ClientParticleTexRef m_texture;
|
||||
video::SMaterial m_material;
|
||||
v2f m_texpos;
|
||||
v2f m_texsize;
|
||||
v3f m_pos;
|
||||
v3f m_velocity;
|
||||
v3f m_acceleration;
|
||||
const ParticleParameters m_p;
|
||||
LocalPlayer *m_player;
|
||||
|
||||
//! Color without lighting
|
||||
video::SColor m_base_color;
|
||||
//! Final rendered color
|
||||
video::SColor m_color;
|
||||
const ParticleParameters m_p;
|
||||
|
||||
float m_animation_time = 0.0f;
|
||||
int m_animation_frame = 0;
|
||||
float m_alpha = 0.0f;
|
||||
|
||||
ParticleSpawner *m_parent = nullptr;
|
||||
// Used if not spawned from a particlespawner
|
||||
|
@ -142,8 +128,7 @@ private:
|
|||
class ParticleSpawner
|
||||
{
|
||||
public:
|
||||
ParticleSpawner(IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ParticleSpawner(LocalPlayer *player,
|
||||
const ParticleSpawnerParameters ¶ms,
|
||||
u16 attached_id,
|
||||
std::vector<ClientParticleTexture> &&texpool,
|
||||
|
@ -164,7 +149,6 @@ private:
|
|||
size_t m_active;
|
||||
ParticleManager *m_particlemanager;
|
||||
float m_time;
|
||||
IGameDef *m_gamedef;
|
||||
LocalPlayer *m_player;
|
||||
ParticleSpawnerParameters p;
|
||||
std::vector<ClientParticleTexture> m_texpool;
|
||||
|
@ -172,12 +156,61 @@ private:
|
|||
u16 m_attached_id;
|
||||
};
|
||||
|
||||
class ParticleBuffer : public scene::ISceneNode
|
||||
{
|
||||
friend class ParticleManager;
|
||||
public:
|
||||
ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material);
|
||||
|
||||
// for pointer stability
|
||||
DISABLE_CLASS_COPY(ParticleBuffer)
|
||||
|
||||
/// Reserves one more slot for a particle (4 vertices, 6 indices)
|
||||
/// @return particle index within buffer
|
||||
std::optional<u16> allocate();
|
||||
/// Frees the particle at `index`
|
||||
void release(u16 index);
|
||||
|
||||
/// @return video::S3DVertex[4]
|
||||
video::S3DVertex *getVertices(u16 index);
|
||||
|
||||
inline bool isEmpty() const {
|
||||
return m_free_list.size() == m_count;
|
||||
}
|
||||
|
||||
virtual video::SMaterial &getMaterial(u32 num) override {
|
||||
return m_mesh_buffer->getMaterial();
|
||||
}
|
||||
virtual u32 getMaterialCount() const override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual const core::aabbox3df &getBoundingBox() const override;
|
||||
|
||||
virtual void render() override;
|
||||
|
||||
virtual void OnRegisterSceneNode() override;
|
||||
|
||||
// we have 16-bit indices
|
||||
static constexpr u16 MAX_PARTICLES_PER_BUFFER = 16000;
|
||||
|
||||
private:
|
||||
irr_ptr<scene::SMeshBuffer> m_mesh_buffer;
|
||||
// unused (e.g. expired) particle indices for re-use
|
||||
std::vector<u16> m_free_list;
|
||||
// for automatic deletion when unused for a while. is reset on allocate().
|
||||
float m_usage_timer = 0;
|
||||
// total count of contained particles
|
||||
u16 m_count = 0;
|
||||
mutable bool m_bounding_box_dirty = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class doing particle as well as their spawners handling
|
||||
*/
|
||||
class ParticleManager
|
||||
{
|
||||
friend class ParticleSpawner;
|
||||
friend class ParticleSpawner;
|
||||
public:
|
||||
ParticleManager(ClientEnvironment* env);
|
||||
DISABLE_CLASS_COPY(ParticleManager)
|
||||
|
@ -213,7 +246,9 @@ protected:
|
|||
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
|
||||
v2f &texsize, video::SColor *color, u8 tilenum = 0);
|
||||
|
||||
void addParticle(std::unique_ptr<Particle> toadd);
|
||||
static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture);
|
||||
|
||||
bool addParticle(std::unique_ptr<Particle> toadd);
|
||||
|
||||
private:
|
||||
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
|
||||
|
@ -221,17 +256,23 @@ private:
|
|||
|
||||
void stepParticles(float dtime);
|
||||
void stepSpawners(float dtime);
|
||||
void stepBuffers(float dtime);
|
||||
|
||||
void clearAll();
|
||||
|
||||
std::vector<std::unique_ptr<Particle>> m_particles;
|
||||
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
|
||||
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
|
||||
// Start the particle spawner ids generated from here after u32_max. lower values are
|
||||
// for server sent spawners.
|
||||
u64 m_next_particle_spawner_id = U32_MAX + 1;
|
||||
std::vector<irr_ptr<ParticleBuffer>> m_particle_buffers;
|
||||
|
||||
// Start the particle spawner ids generated from here after u32_max.
|
||||
// lower values are for server sent spawners.
|
||||
u64 m_next_particle_spawner_id = static_cast<u64>(U32_MAX) + 1;
|
||||
|
||||
ClientEnvironment *m_env;
|
||||
|
||||
IntervalLimiter m_buffer_gc;
|
||||
|
||||
std::mutex m_particle_list_lock;
|
||||
std::mutex m_spawner_list_lock;
|
||||
};
|
||||
|
|
|
@ -164,13 +164,19 @@ static std::optional<video::E_DRIVER_TYPE> chooseVideoDriver()
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
static inline auto getVideoDriverName(video::E_DRIVER_TYPE driver)
|
||||
{
|
||||
return RenderingEngine::getVideoDriverInfo(driver).friendly_name;
|
||||
}
|
||||
|
||||
static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std::optional<video::E_DRIVER_TYPE> requested_driver)
|
||||
{
|
||||
if (requested_driver) {
|
||||
params.DriverType = *requested_driver;
|
||||
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
|
||||
if (auto *device = createDeviceEx(params))
|
||||
return device;
|
||||
errorstream << "Failed to initialize the " << RenderingEngine::getVideoDriverInfo(*requested_driver).friendly_name << " video driver" << std::endl;
|
||||
errorstream << "Failed to initialize the " << getVideoDriverName(params.DriverType) << " video driver" << std::endl;
|
||||
}
|
||||
sanity_check(requested_driver != video::EDT_NULL);
|
||||
|
||||
|
@ -179,6 +185,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std
|
|||
if (fallback_driver == video::EDT_NULL || fallback_driver == requested_driver)
|
||||
continue;
|
||||
params.DriverType = fallback_driver;
|
||||
verbosestream << "Trying video driver " << getVideoDriverName(params.DriverType) << std::endl;
|
||||
if (auto *device = createDeviceEx(params))
|
||||
return device;
|
||||
}
|
||||
|
@ -222,9 +229,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
|
|||
params.Stencilbuffer = false;
|
||||
params.Vsync = vsync;
|
||||
params.EventReceiver = receiver;
|
||||
#ifdef __ANDROID__
|
||||
params.PrivateData = porting::app_global;
|
||||
#endif
|
||||
|
||||
// there is no standardized path for these on desktop
|
||||
std::string rel_path = std::string("client") + DIR_DELIM
|
||||
+ "shaders" + DIR_DELIM + "Irrlicht";
|
||||
|
@ -232,7 +237,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
|
|||
|
||||
m_device = createDevice(params, driverType);
|
||||
driver = m_device->getVideoDriver();
|
||||
infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl;
|
||||
verbosestream << "Using the " << getVideoDriverName(driver->getDriverType()) << " video driver" << std::endl;
|
||||
|
||||
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
|
||||
driver->setMinHardwareBufferVertexCount(4);
|
||||
|
|
|
@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
#include "al_extensions.h"
|
||||
#include "debug.h"
|
||||
#include "sound_constants.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
|
@ -77,33 +78,27 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
|
|||
warn_if_al_error("when creating non-streaming sound");
|
||||
|
||||
} else {
|
||||
// Start with 2 buffers
|
||||
ALuint buf_ids[2];
|
||||
// Start with first buffer
|
||||
|
||||
// If m_next_sample_pos >= len_samples (happens only if not looped), one
|
||||
// or both of buf_ids will be 0. Queuing 0 is a NOP.
|
||||
// If m_next_sample_pos >= len_samples (happens only if not looped), buf0
|
||||
// will be 0. Queuing 0 is a NOP.
|
||||
|
||||
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||
buf_ids[0] = buf0;
|
||||
m_next_sample_pos = buf0_end;
|
||||
|
||||
if (m_looping && m_next_sample_pos == len_samples)
|
||||
m_next_sample_pos = 0;
|
||||
|
||||
auto [buf1, buf1_end, offset_in_buf1] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||
buf_ids[1] = buf1;
|
||||
m_next_sample_pos = buf1_end;
|
||||
assert(offset_in_buf1 == 0);
|
||||
|
||||
alSourceQueueBuffers(m_source_id, 2, buf_ids);
|
||||
alSourceQueueBuffers(m_source_id, 1, &buf0);
|
||||
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
|
||||
|
||||
// We can't use AL_LOOPING because more buffers are queued later
|
||||
// looping is therefore done manually
|
||||
// We can't use AL_LOOPING because more buffers are queued later.
|
||||
// Looping is therefore done manually.
|
||||
|
||||
// Sound is not dead if queue runs empty prematurely
|
||||
m_stopped_means_dead = false;
|
||||
|
||||
warn_if_al_error("when creating streaming sound");
|
||||
|
||||
// Enqueue more buffers
|
||||
stepStream(true);
|
||||
}
|
||||
|
||||
// Set initial pos, volume, pitch
|
||||
|
@ -129,23 +124,44 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
|
|||
setPitch(pitch);
|
||||
}
|
||||
|
||||
bool PlayingSound::stepStream()
|
||||
bool PlayingSound::stepStream(bool playback_speed_changed)
|
||||
{
|
||||
if (isDead())
|
||||
return false;
|
||||
|
||||
// unqueue finished buffers
|
||||
ALint num_unqueued_bufs = 0;
|
||||
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_unqueued_bufs);
|
||||
if (num_unqueued_bufs == 0)
|
||||
return true;
|
||||
// We always have 2 buffers enqueued at most
|
||||
SANITY_CHECK(num_unqueued_bufs <= 2);
|
||||
ALuint unqueued_buffer_ids[2];
|
||||
alSourceUnqueueBuffers(m_source_id, num_unqueued_bufs, unqueued_buffer_ids);
|
||||
// Unqueue finished buffers
|
||||
ALint num_processed_bufs = 0;
|
||||
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_processed_bufs);
|
||||
if (num_processed_bufs == 0 && !playback_speed_changed)
|
||||
return true; // Nothing to do
|
||||
if (num_processed_bufs > 0) {
|
||||
ALint num_to_unqueue = num_processed_bufs;
|
||||
ALuint unqueued_buffer_ids[8];
|
||||
while (num_to_unqueue > 8) {
|
||||
alSourceUnqueueBuffers(m_source_id, 8, unqueued_buffer_ids);
|
||||
num_to_unqueue -= 8;
|
||||
}
|
||||
alSourceUnqueueBuffers(m_source_id, num_to_unqueue, unqueued_buffer_ids);
|
||||
}
|
||||
|
||||
// Fill up again
|
||||
for (ALint i = 0; i < num_unqueued_bufs; ++i) {
|
||||
// Find out how many buffers we want to enqueue
|
||||
f32 pitch = 1.0f;
|
||||
alGetSourcef(m_source_id, AL_PITCH, &pitch);
|
||||
ALint num_queued_bufs = 0;
|
||||
alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &num_queued_bufs);
|
||||
// Min. length of untouched buffers
|
||||
const f32 playback_left = MIN_STREAM_BUFFER_LENGTH * std::max(0, num_queued_bufs - 1);
|
||||
// Max. time until next stepStream() call, see also [Streaming of sounds] in
|
||||
// sound_constants.h.
|
||||
// Multiplied by pitch because pitch makes playback faster than real time.
|
||||
// (Does not account for doppler effect, if we had that.)
|
||||
// +0.1 seconds to accommodate hickups.
|
||||
const f32 playback_until_next_check = (2.0f * STREAM_BIGSTEP_TIME + 0.1f) * pitch;
|
||||
const f32 playback_to_fill_up = std::max(0.0f, playback_until_next_check - playback_left);
|
||||
const int num_bufs_to_enqueue = std::ceil(playback_to_fill_up / MIN_STREAM_BUFFER_LENGTH);
|
||||
|
||||
// Fill up
|
||||
for (int i = 0; i < num_bufs_to_enqueue; ++i) {
|
||||
if (m_next_sample_pos == m_data->m_decode_info.length_samples) {
|
||||
// Reached end
|
||||
if (m_looping) {
|
||||
|
@ -256,4 +272,11 @@ f32 PlayingSound::getGain() noexcept
|
|||
return gain;
|
||||
}
|
||||
|
||||
void PlayingSound::setPitch(f32 pitch)
|
||||
{
|
||||
alSourcef(m_source_id, AL_PITCH, pitch);
|
||||
if (isStreaming())
|
||||
stepStream(true);
|
||||
}
|
||||
|
||||
} // namespace sound
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
DISABLE_CLASS_COPY(PlayingSound)
|
||||
|
||||
// return false means streaming finished
|
||||
bool stepStream();
|
||||
bool stepStream(bool playback_speed_changed = false);
|
||||
|
||||
// retruns true if it wasn't fading already
|
||||
bool fade(f32 step, f32 target_gain) noexcept;
|
||||
|
@ -77,7 +77,7 @@ public:
|
|||
|
||||
f32 getGain() noexcept;
|
||||
|
||||
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
|
||||
void setPitch(f32 pitch);
|
||||
|
||||
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
||||
|
||||
|
|
|
@ -89,14 +89,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
||||
* end of the next bigstep. So between two stepStream()-calls lie at most
|
||||
* 2 * STREAM_BIGSTEP_TIME seconds.
|
||||
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
|
||||
* is still available after the first stepStream().
|
||||
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
|
||||
* not run into an empty queue.
|
||||
*
|
||||
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
|
||||
* other sounds that may have taken long to stepStream(), and sounds being played
|
||||
* faster due to Doppler effect.
|
||||
* We ensure that there are always enough untouched full buffers left such that
|
||||
* we do not run into an empty queue in this time period, see stepStream().
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -115,8 +109,6 @@ constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
|
|||
// step duration for the OpenALSoundManager thread, in seconds
|
||||
constexpr f32 SOUNDTHREAD_DTIME = 0.016f;
|
||||
|
||||
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
|
||||
"See [Streaming of sounds].");
|
||||
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
||||
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "nodetimer.h"
|
||||
#include "inventory.h"
|
||||
#include "log.h"
|
||||
#include "debug.h"
|
||||
#include "serialization.h"
|
||||
#include "util/serialize.h"
|
||||
#include "util/string.h"
|
||||
|
|
|
@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <cassert>
|
||||
#include "gettime.h"
|
||||
|
|
|
@ -310,6 +310,7 @@ void set_default_settings()
|
|||
settings->setDefault("invert_hotbar_mouse_wheel", "false");
|
||||
settings->setDefault("mouse_sensitivity", "0.2");
|
||||
settings->setDefault("repeat_place_time", "0.25");
|
||||
settings->setDefault("repeat_dig_time", "0.15");
|
||||
settings->setDefault("safe_dig_and_place", "false");
|
||||
settings->setDefault("random_input", "false");
|
||||
settings->setDefault("aux1_descends", "false");
|
||||
|
@ -364,11 +365,10 @@ void set_default_settings()
|
|||
settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default");
|
||||
#endif
|
||||
|
||||
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
|
||||
#if ENABLE_UPDATE_CHECKER
|
||||
settings->setDefault("update_last_checked", "");
|
||||
settings->setDefault("update_information_url", "https://www.minetest.net/release_info.json");
|
||||
#else
|
||||
settings->setDefault("update_last_checked", "disabled");
|
||||
settings->setDefault("update_information_url", "");
|
||||
#endif
|
||||
|
||||
// Server
|
||||
|
@ -494,11 +494,13 @@ void set_default_settings()
|
|||
settings->setDefault("keymap_sneak", "KEY_SHIFT");
|
||||
#endif
|
||||
|
||||
settings->setDefault("touchscreen_threshold", "20");
|
||||
settings->setDefault("touchscreen_sensitivity", "0.2");
|
||||
settings->setDefault("touchscreen_threshold", "20");
|
||||
settings->setDefault("touch_long_tap_delay", "400");
|
||||
settings->setDefault("touch_use_crosshair", "false");
|
||||
settings->setDefault("fixed_virtual_joystick", "false");
|
||||
settings->setDefault("virtual_joystick_triggers_aux1", "false");
|
||||
settings->setDefault("touch_punch_gesture", "short_tap");
|
||||
#ifdef ENABLE_TOUCH
|
||||
settings->setDefault("clickable_chat_weblinks", "false");
|
||||
#else
|
||||
|
|
|
@ -223,6 +223,16 @@ std::string CreateTempFile()
|
|||
return path;
|
||||
}
|
||||
|
||||
std::string CreateTempDir()
|
||||
{
|
||||
std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
|
||||
_mktemp_s(&path[0], path.size() + 1); // modifies path
|
||||
// will error if it already exists
|
||||
if (!CreateDirectory(path.c_str(), nullptr))
|
||||
return "";
|
||||
return path;
|
||||
}
|
||||
|
||||
bool CopyFileContents(const std::string &source, const std::string &target)
|
||||
{
|
||||
BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr,
|
||||
|
@ -446,6 +456,15 @@ std::string CreateTempFile()
|
|||
return path;
|
||||
}
|
||||
|
||||
std::string CreateTempDir()
|
||||
{
|
||||
std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
|
||||
auto r = mkdtemp(&path[0]); // modifies path
|
||||
if (!r)
|
||||
return "";
|
||||
return path;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct FileDeleter {
|
||||
void operator()(FILE *stream) {
|
||||
|
|
|
@ -75,13 +75,19 @@ bool RecursiveDelete(const std::string &path);
|
|||
|
||||
bool DeleteSingleFileOrEmptyDirectory(const std::string &path);
|
||||
|
||||
// Returns path to temp directory, can return "" on error
|
||||
/// Returns path to temp directory.
|
||||
/// You probably don't want to use this directly, see `CreateTempFile` or `CreateTempDir`.
|
||||
/// @return path or "" on error
|
||||
std::string TempPath();
|
||||
|
||||
// Returns path to securely-created temporary file (will already exist when this function returns)
|
||||
// can return "" on error
|
||||
/// Returns path to securely-created temporary file (will already exist when this function returns).
|
||||
/// @return path or "" on error
|
||||
std::string CreateTempFile();
|
||||
|
||||
/// Returns path to securely-created temporary directory (will already exist when this function returns).
|
||||
/// @return path or "" on error
|
||||
std::string CreateTempDir();
|
||||
|
||||
/* Returns a list of subdirectories, including the path itself, but excluding
|
||||
hidden directories (whose names start with . or _)
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,280 +1,280 @@
|
|||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IGUIStaticText.h>
|
||||
#include "irrlicht_changes/static_text.h"
|
||||
#include "IGUIButton.h"
|
||||
#include "IGUISpriteBank.h"
|
||||
#include "ITexture.h"
|
||||
#include "SColor.h"
|
||||
#include "guiSkin.h"
|
||||
#include "StyleSpec.h"
|
||||
|
||||
using namespace irr;
|
||||
|
||||
class ISimpleTextureSource;
|
||||
|
||||
class GUIButton : public gui::IGUIButton
|
||||
{
|
||||
public:
|
||||
|
||||
//! constructor
|
||||
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
|
||||
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
|
||||
bool noclip=false);
|
||||
|
||||
//! destructor
|
||||
virtual ~GUIButton();
|
||||
|
||||
//! called if an event happened.
|
||||
virtual bool OnEvent(const SEvent& event) override;
|
||||
|
||||
//! draws the element and its children
|
||||
virtual void draw() override;
|
||||
|
||||
//! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
|
||||
virtual void setOverrideFont(gui::IGUIFont* font=0) override;
|
||||
|
||||
//! Gets the override font (if any)
|
||||
virtual gui::IGUIFont* getOverrideFont() const override;
|
||||
|
||||
//! Get the font which is used right now for drawing
|
||||
virtual gui::IGUIFont* getActiveFont() const override;
|
||||
|
||||
//! Sets another color for the button text.
|
||||
virtual void setOverrideColor(video::SColor color) override;
|
||||
|
||||
//! Gets the override color
|
||||
virtual video::SColor getOverrideColor() const override;
|
||||
|
||||
//! Gets the currently used text color
|
||||
virtual video::SColor getActiveColor() const override;
|
||||
|
||||
//! Sets if the button text should use the override color or the color in the gui skin.
|
||||
virtual void enableOverrideColor(bool enable) override;
|
||||
|
||||
//! Checks if an override color is enabled
|
||||
virtual bool isOverrideColorEnabled(void) const override;
|
||||
|
||||
// PATCH
|
||||
//! Sets an image which should be displayed on the button when it is in the given state.
|
||||
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
|
||||
video::ITexture* image=nullptr,
|
||||
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0)) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||
virtual void setImage(video::ITexture* image=nullptr) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||
virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||
virtual void setPressedImage(video::ITexture* image=nullptr) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||
virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||
|
||||
//! Sets the text displayed by the button
|
||||
virtual void setText(const wchar_t* text) override;
|
||||
// END PATCH
|
||||
|
||||
//! Sets the sprite bank used by the button
|
||||
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
|
||||
|
||||
//! Sets the animated sprite for a specific button state
|
||||
/** \param index: Number of the sprite within the sprite bank, use -1 for no sprite
|
||||
\param state: State of the button to set the sprite for
|
||||
\param index: The sprite number from the current sprite bank
|
||||
\param color: The color of the sprite
|
||||
*/
|
||||
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
|
||||
video::SColor color=video::SColor(255,255,255,255),
|
||||
bool loop=false, bool scale=false) override;
|
||||
|
||||
//! Get the sprite-index for the given state or -1 when no sprite is set
|
||||
virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Get the sprite color for the given state. Color is only used when a sprite is set.
|
||||
virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Returns if the sprite in the given state does loop
|
||||
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Returns if the sprite in the given state is scaled
|
||||
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Sets if the button should behave like a push button. Which means it
|
||||
//! can be in two states: Normal or Pressed. With a click on the button,
|
||||
//! the user can change the state of the button.
|
||||
virtual void setIsPushButton(bool isPushButton=true) override;
|
||||
|
||||
//! Checks whether the button is a push button
|
||||
virtual bool isPushButton() const override;
|
||||
|
||||
//! Sets the pressed state of the button if this is a pushbutton
|
||||
virtual void setPressed(bool pressed=true) override;
|
||||
|
||||
//! Returns if the button is currently pressed
|
||||
virtual bool isPressed() const override;
|
||||
|
||||
// PATCH
|
||||
//! Returns if this element (or one of its direct children) is hovered
|
||||
bool isHovered() const;
|
||||
|
||||
//! Returns if this element (or one of its direct children) is focused
|
||||
bool isFocused() const;
|
||||
// END PATCH
|
||||
|
||||
//! Sets if the button should use the skin to draw its border
|
||||
virtual void setDrawBorder(bool border=true) override;
|
||||
|
||||
//! Checks if the button face and border are being drawn
|
||||
virtual bool isDrawingBorder() const override;
|
||||
|
||||
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
|
||||
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
|
||||
|
||||
//! Checks if the alpha channel should be used for drawing images on the button
|
||||
virtual bool isAlphaChannelUsed() const override;
|
||||
|
||||
//! Sets if the button should scale the button images to fit
|
||||
virtual void setScaleImage(bool scaleImage=true) override;
|
||||
|
||||
//! Checks whether the button scales the used images
|
||||
virtual bool isScalingImage() const override;
|
||||
|
||||
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
|
||||
virtual bool getClickShiftState() const override
|
||||
{
|
||||
return ClickShiftState;
|
||||
}
|
||||
|
||||
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
|
||||
virtual bool getClickControlState() const override
|
||||
{
|
||||
return ClickControlState;
|
||||
}
|
||||
|
||||
void setColor(video::SColor color);
|
||||
// PATCH
|
||||
//! Set element properties from a StyleSpec corresponding to the button state
|
||||
void setFromState();
|
||||
|
||||
//! Set element properties from a StyleSpec
|
||||
virtual void setFromStyle(const StyleSpec& style);
|
||||
|
||||
//! Set the styles used for each state
|
||||
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
|
||||
// END PATCH
|
||||
|
||||
|
||||
//! Do not drop returned handle
|
||||
static GUIButton* addButton(gui::IGUIEnvironment *environment,
|
||||
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
|
||||
IGUIElement* parent, s32 id, const wchar_t* text,
|
||||
const wchar_t *tooltiptext=L"");
|
||||
|
||||
protected:
|
||||
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
|
||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
|
||||
|
||||
ISimpleTextureSource *getTextureSource() { return TSrc; }
|
||||
|
||||
struct ButtonImage
|
||||
{
|
||||
ButtonImage() = default;
|
||||
|
||||
ButtonImage(const ButtonImage& other)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
~ButtonImage()
|
||||
{
|
||||
if ( Texture )
|
||||
Texture->drop();
|
||||
}
|
||||
|
||||
ButtonImage& operator=(const ButtonImage& other)
|
||||
{
|
||||
if ( this == &other )
|
||||
return *this;
|
||||
|
||||
if (other.Texture)
|
||||
other.Texture->grab();
|
||||
if ( Texture )
|
||||
Texture->drop();
|
||||
Texture = other.Texture;
|
||||
SourceRect = other.SourceRect;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const ButtonImage& other) const
|
||||
{
|
||||
return Texture == other.Texture && SourceRect == other.SourceRect;
|
||||
}
|
||||
|
||||
|
||||
video::ITexture* Texture = nullptr;
|
||||
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
|
||||
};
|
||||
|
||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
|
||||
|
||||
private:
|
||||
|
||||
struct ButtonSprite
|
||||
{
|
||||
bool operator==(const ButtonSprite &other) const
|
||||
{
|
||||
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
|
||||
}
|
||||
|
||||
s32 Index = -1;
|
||||
video::SColor Color;
|
||||
bool Loop = false;
|
||||
bool Scale = false;
|
||||
};
|
||||
|
||||
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
|
||||
gui::IGUISpriteBank* SpriteBank = nullptr;
|
||||
|
||||
ButtonImage ButtonImages[gui::EGBIS_COUNT];
|
||||
|
||||
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
|
||||
|
||||
gui::IGUIFont* OverrideFont = nullptr;
|
||||
|
||||
bool OverrideColorEnabled = false;
|
||||
video::SColor OverrideColor = video::SColor(101,255,255,255);
|
||||
|
||||
u32 ClickTime = 0;
|
||||
u32 HoverTime = 0;
|
||||
u32 FocusTime = 0;
|
||||
|
||||
bool ClickShiftState = false;
|
||||
bool ClickControlState = false;
|
||||
|
||||
bool IsPushButton = false;
|
||||
bool Pressed = false;
|
||||
bool UseAlphaChannel = false;
|
||||
bool DrawBorder = true;
|
||||
bool ScaleImage = false;
|
||||
|
||||
video::SColor Colors[4];
|
||||
// PATCH
|
||||
bool WasHovered = false;
|
||||
bool WasFocused = false;
|
||||
ISimpleTextureSource *TSrc;
|
||||
|
||||
gui::IGUIStaticText *StaticText;
|
||||
|
||||
core::rect<s32> BgMiddle;
|
||||
core::rect<s32> Padding;
|
||||
core::vector2d<s32> ContentOffset;
|
||||
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
|
||||
// END PATCH
|
||||
};
|
||||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <IGUIStaticText.h>
|
||||
#include "irrlicht_changes/static_text.h"
|
||||
#include "IGUIButton.h"
|
||||
#include "IGUISpriteBank.h"
|
||||
#include "ITexture.h"
|
||||
#include "SColor.h"
|
||||
#include "guiSkin.h"
|
||||
#include "StyleSpec.h"
|
||||
|
||||
using namespace irr;
|
||||
|
||||
class ISimpleTextureSource;
|
||||
|
||||
class GUIButton : public gui::IGUIButton
|
||||
{
|
||||
public:
|
||||
|
||||
//! constructor
|
||||
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
|
||||
s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
|
||||
bool noclip=false);
|
||||
|
||||
//! destructor
|
||||
virtual ~GUIButton();
|
||||
|
||||
//! called if an event happened.
|
||||
virtual bool OnEvent(const SEvent& event) override;
|
||||
|
||||
//! draws the element and its children
|
||||
virtual void draw() override;
|
||||
|
||||
//! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
|
||||
virtual void setOverrideFont(gui::IGUIFont* font=0) override;
|
||||
|
||||
//! Gets the override font (if any)
|
||||
virtual gui::IGUIFont* getOverrideFont() const override;
|
||||
|
||||
//! Get the font which is used right now for drawing
|
||||
virtual gui::IGUIFont* getActiveFont() const override;
|
||||
|
||||
//! Sets another color for the button text.
|
||||
virtual void setOverrideColor(video::SColor color) override;
|
||||
|
||||
//! Gets the override color
|
||||
virtual video::SColor getOverrideColor() const override;
|
||||
|
||||
//! Gets the currently used text color
|
||||
virtual video::SColor getActiveColor() const override;
|
||||
|
||||
//! Sets if the button text should use the override color or the color in the gui skin.
|
||||
virtual void enableOverrideColor(bool enable) override;
|
||||
|
||||
//! Checks if an override color is enabled
|
||||
virtual bool isOverrideColorEnabled(void) const override;
|
||||
|
||||
// PATCH
|
||||
//! Sets an image which should be displayed on the button when it is in the given state.
|
||||
virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
|
||||
video::ITexture* image=nullptr,
|
||||
const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0)) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||
virtual void setImage(video::ITexture* image=nullptr) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in normal state.
|
||||
virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||
virtual void setPressedImage(video::ITexture* image=nullptr) override;
|
||||
|
||||
//! Sets an image which should be displayed on the button when it is in pressed state.
|
||||
virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
|
||||
|
||||
//! Sets the text displayed by the button
|
||||
virtual void setText(const wchar_t* text) override;
|
||||
// END PATCH
|
||||
|
||||
//! Sets the sprite bank used by the button
|
||||
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
|
||||
|
||||
//! Sets the animated sprite for a specific button state
|
||||
/** \param index: Number of the sprite within the sprite bank, use -1 for no sprite
|
||||
\param state: State of the button to set the sprite for
|
||||
\param index: The sprite number from the current sprite bank
|
||||
\param color: The color of the sprite
|
||||
*/
|
||||
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
|
||||
video::SColor color=video::SColor(255,255,255,255),
|
||||
bool loop=false, bool scale=false) override;
|
||||
|
||||
//! Get the sprite-index for the given state or -1 when no sprite is set
|
||||
virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Get the sprite color for the given state. Color is only used when a sprite is set.
|
||||
virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Returns if the sprite in the given state does loop
|
||||
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Returns if the sprite in the given state is scaled
|
||||
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
|
||||
|
||||
//! Sets if the button should behave like a push button. Which means it
|
||||
//! can be in two states: Normal or Pressed. With a click on the button,
|
||||
//! the user can change the state of the button.
|
||||
virtual void setIsPushButton(bool isPushButton=true) override;
|
||||
|
||||
//! Checks whether the button is a push button
|
||||
virtual bool isPushButton() const override;
|
||||
|
||||
//! Sets the pressed state of the button if this is a pushbutton
|
||||
virtual void setPressed(bool pressed=true) override;
|
||||
|
||||
//! Returns if the button is currently pressed
|
||||
virtual bool isPressed() const override;
|
||||
|
||||
// PATCH
|
||||
//! Returns if this element (or one of its direct children) is hovered
|
||||
bool isHovered() const;
|
||||
|
||||
//! Returns if this element (or one of its direct children) is focused
|
||||
bool isFocused() const;
|
||||
// END PATCH
|
||||
|
||||
//! Sets if the button should use the skin to draw its border
|
||||
virtual void setDrawBorder(bool border=true) override;
|
||||
|
||||
//! Checks if the button face and border are being drawn
|
||||
virtual bool isDrawingBorder() const override;
|
||||
|
||||
//! Sets if the alpha channel should be used for drawing images on the button (default is false)
|
||||
virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
|
||||
|
||||
//! Checks if the alpha channel should be used for drawing images on the button
|
||||
virtual bool isAlphaChannelUsed() const override;
|
||||
|
||||
//! Sets if the button should scale the button images to fit
|
||||
virtual void setScaleImage(bool scaleImage=true) override;
|
||||
|
||||
//! Checks whether the button scales the used images
|
||||
virtual bool isScalingImage() const override;
|
||||
|
||||
//! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
|
||||
virtual bool getClickShiftState() const override
|
||||
{
|
||||
return ClickShiftState;
|
||||
}
|
||||
|
||||
//! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
|
||||
virtual bool getClickControlState() const override
|
||||
{
|
||||
return ClickControlState;
|
||||
}
|
||||
|
||||
void setColor(video::SColor color);
|
||||
// PATCH
|
||||
//! Set element properties from a StyleSpec corresponding to the button state
|
||||
void setFromState();
|
||||
|
||||
//! Set element properties from a StyleSpec
|
||||
virtual void setFromStyle(const StyleSpec& style);
|
||||
|
||||
//! Set the styles used for each state
|
||||
void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
|
||||
// END PATCH
|
||||
|
||||
|
||||
//! Do not drop returned handle
|
||||
static GUIButton* addButton(gui::IGUIEnvironment *environment,
|
||||
const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
|
||||
IGUIElement* parent, s32 id, const wchar_t* text,
|
||||
const wchar_t *tooltiptext=L"");
|
||||
|
||||
protected:
|
||||
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
|
||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
|
||||
|
||||
ISimpleTextureSource *getTextureSource() { return TSrc; }
|
||||
|
||||
struct ButtonImage
|
||||
{
|
||||
ButtonImage() = default;
|
||||
|
||||
ButtonImage(const ButtonImage& other)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
~ButtonImage()
|
||||
{
|
||||
if ( Texture )
|
||||
Texture->drop();
|
||||
}
|
||||
|
||||
ButtonImage& operator=(const ButtonImage& other)
|
||||
{
|
||||
if ( this == &other )
|
||||
return *this;
|
||||
|
||||
if (other.Texture)
|
||||
other.Texture->grab();
|
||||
if ( Texture )
|
||||
Texture->drop();
|
||||
Texture = other.Texture;
|
||||
SourceRect = other.SourceRect;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const ButtonImage& other) const
|
||||
{
|
||||
return Texture == other.Texture && SourceRect == other.SourceRect;
|
||||
}
|
||||
|
||||
|
||||
video::ITexture* Texture = nullptr;
|
||||
core::rect<s32> SourceRect = core::rect<s32>(0,0,0,0);
|
||||
};
|
||||
|
||||
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed, const ButtonImage* images) const;
|
||||
|
||||
private:
|
||||
|
||||
struct ButtonSprite
|
||||
{
|
||||
bool operator==(const ButtonSprite &other) const
|
||||
{
|
||||
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
|
||||
}
|
||||
|
||||
s32 Index = -1;
|
||||
video::SColor Color;
|
||||
bool Loop = false;
|
||||
bool Scale = false;
|
||||
};
|
||||
|
||||
ButtonSprite ButtonSprites[gui::EGBS_COUNT];
|
||||
gui::IGUISpriteBank* SpriteBank = nullptr;
|
||||
|
||||
ButtonImage ButtonImages[gui::EGBIS_COUNT];
|
||||
|
||||
std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
|
||||
|
||||
gui::IGUIFont* OverrideFont = nullptr;
|
||||
|
||||
bool OverrideColorEnabled = false;
|
||||
video::SColor OverrideColor = video::SColor(101,255,255,255);
|
||||
|
||||
u32 ClickTime = 0;
|
||||
u32 HoverTime = 0;
|
||||
u32 FocusTime = 0;
|
||||
|
||||
bool ClickShiftState = false;
|
||||
bool ClickControlState = false;
|
||||
|
||||
bool IsPushButton = false;
|
||||
bool Pressed = false;
|
||||
bool UseAlphaChannel = false;
|
||||
bool DrawBorder = true;
|
||||
bool ScaleImage = false;
|
||||
|
||||
video::SColor Colors[4];
|
||||
// PATCH
|
||||
bool WasHovered = false;
|
||||
bool WasFocused = false;
|
||||
ISimpleTextureSource *TSrc;
|
||||
|
||||
gui::IGUIStaticText *StaticText;
|
||||
|
||||
core::rect<s32> BgMiddle;
|
||||
core::rect<s32> Padding;
|
||||
core::vector2d<s32> ContentOffset;
|
||||
video::SColor BgColor = video::SColor(0xFF,0xFF,0xFF,0xFF);
|
||||
// END PATCH
|
||||
};
|
||||
|
|
|
@ -369,6 +369,8 @@ void GUIEngine::run()
|
|||
#endif
|
||||
}
|
||||
|
||||
m_script->beforeClose();
|
||||
|
||||
RenderingEngine::autosaveScreensizeAndCo(initial_screen_size, initial_window_maximized);
|
||||
}
|
||||
|
||||
|
|
2074
src/gui/guiSkin.cpp
2074
src/gui/guiSkin.cpp
File diff suppressed because it is too large
Load Diff
|
@ -1,360 +1,360 @@
|
|||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#ifndef __GUI_SKIN_H_INCLUDED__
|
||||
#define __GUI_SKIN_H_INCLUDED__
|
||||
|
||||
#include "IGUISkin.h"
|
||||
#include "irrString.h"
|
||||
#include <string>
|
||||
#include "ITexture.h"
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
class IVideoDriver;
|
||||
}
|
||||
namespace gui
|
||||
{
|
||||
class GUISkin : public IGUISkin
|
||||
{
|
||||
public:
|
||||
|
||||
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
|
||||
|
||||
//! destructor
|
||||
virtual ~GUISkin();
|
||||
|
||||
//! returns default color
|
||||
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
|
||||
|
||||
//! sets a default color
|
||||
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
|
||||
|
||||
//! returns size for the given size type
|
||||
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
|
||||
|
||||
//! sets a default size
|
||||
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
|
||||
|
||||
//! returns the default font
|
||||
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
|
||||
|
||||
//! sets a default font
|
||||
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
|
||||
|
||||
//! sets the sprite bank used for drawing icons
|
||||
virtual void setSpriteBank(IGUISpriteBank* bank);
|
||||
|
||||
//! gets the sprite bank used for drawing icons
|
||||
virtual IGUISpriteBank* getSpriteBank() const;
|
||||
|
||||
//! Returns a default icon
|
||||
/** Returns the sprite index within the sprite bank */
|
||||
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
|
||||
|
||||
//! Sets a default icon
|
||||
/** Sets the sprite index used for drawing icons like arrows,
|
||||
close buttons and ticks in checkboxes
|
||||
\param icon: Enum specifying which icon to change
|
||||
\param index: The sprite index used to draw this icon */
|
||||
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
|
||||
|
||||
//! Returns a default text.
|
||||
/** For example for Message box button captions:
|
||||
"OK", "Cancel", "Yes", "No" and so on. */
|
||||
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
|
||||
|
||||
//! Sets a default text.
|
||||
/** For example for Message box button captions:
|
||||
"OK", "Cancel", "Yes", "No" and so on. */
|
||||
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
|
||||
|
||||
//! draws a standard 3d button pane
|
||||
/** Used for drawing for example buttons in normal state.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly. */
|
||||
virtual void draw3DButtonPaneStandard(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DButtonPaneStandard(element, rect,clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a pressed 3d button pane
|
||||
/** Used for drawing for example buttons in pressed state.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly. */
|
||||
virtual void draw3DButtonPanePressed(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DButtonPanePressed(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a sunken 3d pane
|
||||
/** Used for drawing the background of edit, combo or check boxes.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param bgcolor: Background color.
|
||||
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
|
||||
deep into the ground.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DSunkenPane(IGUIElement* element,
|
||||
video::SColor bgcolor, bool flat,
|
||||
bool fillBackGround,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DSunkenPane(IGUIElement* element,
|
||||
video::SColor bgcolor, bool flat,
|
||||
bool fillBackGround,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a window background
|
||||
/** Used for drawing the background of dialogs and windows.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param titleBarColor: Title color.
|
||||
\param drawTitleBar: True to enable title drawing.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param checkClientArea: When set to non-null the function will not draw anything,
|
||||
but will instead return the clientArea which can be used for drawing by the calling window.
|
||||
That is the area without borders and without titlebar.
|
||||
\return Returns rect where it would be good to draw title bar text. This will
|
||||
work even when checkClientArea is set to a non-null value.*/
|
||||
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
|
||||
bool drawTitleBar, video::SColor titleBarColor,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip,
|
||||
core::rect<s32>* checkClientArea)
|
||||
{
|
||||
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
|
||||
rect, clip, checkClientArea);
|
||||
}
|
||||
|
||||
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
|
||||
bool drawTitleBar, video::SColor titleBarColor,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip,
|
||||
core::rect<s32>* checkClientArea,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a standard 3d menu pane
|
||||
/** Used for drawing for menus and context menus.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DMenuPane(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DMenuPane(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DMenuPane(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a standard 3d tool bar
|
||||
/** Used for drawing for toolbars and menus.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DToolBar(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DToolBar(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DToolBar(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a tab button
|
||||
/** Used for drawing for tab buttons on top of tabs.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param active: Specifies if the tab is currently active.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DTabButton(IGUIElement* element, bool active,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||
{
|
||||
drawColored3DTabButton(element, active, rect, clip, alignment);
|
||||
}
|
||||
|
||||
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a tab control body
|
||||
/** \param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param border: Specifies if the border should be drawn.
|
||||
\param background: Specifies if the background should be drawn.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DTabBody(IGUIElement* element, bool border, bool background,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||
{
|
||||
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
|
||||
}
|
||||
|
||||
virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws an icon, usually from the skin's sprite bank
|
||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||
This parameter is usually not used by IGUISkin, but can be used for example
|
||||
by more complex implementations to find out how to draw the part exactly.
|
||||
\param icon: Specifies the icon to be drawn.
|
||||
\param position: The position to draw the icon
|
||||
\param starttime: The time at the start of the animation
|
||||
\param currenttime: The present time, used to calculate the frame number
|
||||
\param loop: Whether the animation should loop or not
|
||||
\param clip: Clip area. */
|
||||
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||
const core::position2di position,
|
||||
u32 starttime=0, u32 currenttime=0,
|
||||
bool loop=false, const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
|
||||
}
|
||||
|
||||
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||
const core::position2di position,
|
||||
u32 starttime=0, u32 currenttime=0,
|
||||
bool loop=false, const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a 2d rectangle.
|
||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||
This parameter is usually not used by IGUISkin, but can be used for example
|
||||
by more complex implementations to find out how to draw the part exactly.
|
||||
\param color: Color of the rectangle to draw. The alpha component specifies how
|
||||
transparent the rectangle will be.
|
||||
\param pos: Position of the rectangle.
|
||||
\param clip: Pointer to rectangle against which the rectangle will be clipped.
|
||||
If the pointer is null, no clipping will be performed. */
|
||||
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
|
||||
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
|
||||
|
||||
|
||||
//! get the type of this skin
|
||||
virtual EGUI_SKIN_TYPE getType() const;
|
||||
|
||||
//! gets the colors
|
||||
virtual void getColors(video::SColor* colors); // ::PATCH:
|
||||
|
||||
private:
|
||||
|
||||
video::SColor Colors[EGDC_COUNT];
|
||||
s32 Sizes[EGDS_COUNT];
|
||||
u32 Icons[EGDI_COUNT];
|
||||
IGUIFont* Fonts[EGDF_COUNT];
|
||||
IGUISpriteBank* SpriteBank;
|
||||
core::stringw Texts[EGDT_COUNT];
|
||||
video::IVideoDriver* Driver;
|
||||
bool UseGradient;
|
||||
|
||||
EGUI_SKIN_TYPE Type;
|
||||
};
|
||||
|
||||
#define set3DSkinColors(skin, button_color) \
|
||||
{ \
|
||||
skin->setColor(EGDC_3D_FACE, button_color); \
|
||||
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
|
||||
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
|
||||
skin->setColor(EGDC_3D_LIGHT, button_color); \
|
||||
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
|
||||
}
|
||||
|
||||
#define getElementSkinColor(color) \
|
||||
{ \
|
||||
if (!Colors) \
|
||||
{ \
|
||||
IGUISkin* skin = Environment->getSkin(); \
|
||||
if (skin) \
|
||||
return skin->getColor(color); \
|
||||
} \
|
||||
return Colors[color]; \
|
||||
}
|
||||
|
||||
#define setElementSkinColor(which, newColor, shading) \
|
||||
{ \
|
||||
if (!Colors) \
|
||||
{ \
|
||||
Colors = new video::SColor[EGDC_COUNT]; \
|
||||
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
|
||||
if (skin) \
|
||||
skin->getColors(Colors); \
|
||||
} \
|
||||
Colors[which] = newColor; \
|
||||
setShading(Colors[which],shading); \
|
||||
}
|
||||
} // end namespace gui
|
||||
//! Sets the shading
|
||||
inline void setShading(video::SColor &color,f32 s) // :PATCH:
|
||||
{
|
||||
if (s < 1.0f)
|
||||
{
|
||||
color.setRed(color.getRed() * s);
|
||||
color.setGreen(color.getGreen() * s);
|
||||
color.setBlue(color.getBlue() * s);
|
||||
}
|
||||
else if (s > 1.0f)
|
||||
{
|
||||
s -= 1.0f;
|
||||
|
||||
color.setRed(color.getRed() + (255 - color.getRed()) * s);
|
||||
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
|
||||
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
|
||||
}
|
||||
}
|
||||
} // end namespace irr
|
||||
|
||||
#endif
|
||||
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||||
// This file is part of the "Irrlicht Engine".
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#ifndef __GUI_SKIN_H_INCLUDED__
|
||||
#define __GUI_SKIN_H_INCLUDED__
|
||||
|
||||
#include "IGUISkin.h"
|
||||
#include "irrString.h"
|
||||
#include <string>
|
||||
#include "ITexture.h"
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
class IVideoDriver;
|
||||
}
|
||||
namespace gui
|
||||
{
|
||||
class GUISkin : public IGUISkin
|
||||
{
|
||||
public:
|
||||
|
||||
GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
|
||||
|
||||
//! destructor
|
||||
virtual ~GUISkin();
|
||||
|
||||
//! returns default color
|
||||
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
|
||||
|
||||
//! sets a default color
|
||||
virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
|
||||
|
||||
//! returns size for the given size type
|
||||
virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
|
||||
|
||||
//! sets a default size
|
||||
virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
|
||||
|
||||
//! returns the default font
|
||||
virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
|
||||
|
||||
//! sets a default font
|
||||
virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
|
||||
|
||||
//! sets the sprite bank used for drawing icons
|
||||
virtual void setSpriteBank(IGUISpriteBank* bank);
|
||||
|
||||
//! gets the sprite bank used for drawing icons
|
||||
virtual IGUISpriteBank* getSpriteBank() const;
|
||||
|
||||
//! Returns a default icon
|
||||
/** Returns the sprite index within the sprite bank */
|
||||
virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
|
||||
|
||||
//! Sets a default icon
|
||||
/** Sets the sprite index used for drawing icons like arrows,
|
||||
close buttons and ticks in checkboxes
|
||||
\param icon: Enum specifying which icon to change
|
||||
\param index: The sprite index used to draw this icon */
|
||||
virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
|
||||
|
||||
//! Returns a default text.
|
||||
/** For example for Message box button captions:
|
||||
"OK", "Cancel", "Yes", "No" and so on. */
|
||||
virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
|
||||
|
||||
//! Sets a default text.
|
||||
/** For example for Message box button captions:
|
||||
"OK", "Cancel", "Yes", "No" and so on. */
|
||||
virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
|
||||
|
||||
//! draws a standard 3d button pane
|
||||
/** Used for drawing for example buttons in normal state.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly. */
|
||||
virtual void draw3DButtonPaneStandard(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DButtonPaneStandard(element, rect,clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a pressed 3d button pane
|
||||
/** Used for drawing for example buttons in pressed state.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly. */
|
||||
virtual void draw3DButtonPanePressed(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DButtonPanePressed(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DButtonPanePressed(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a sunken 3d pane
|
||||
/** Used for drawing the background of edit, combo or check boxes.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param bgcolor: Background color.
|
||||
\param flat: Specifies if the sunken pane should be flat or displayed as sunken
|
||||
deep into the ground.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DSunkenPane(IGUIElement* element,
|
||||
video::SColor bgcolor, bool flat,
|
||||
bool fillBackGround,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DSunkenPane(IGUIElement* element,
|
||||
video::SColor bgcolor, bool flat,
|
||||
bool fillBackGround,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a window background
|
||||
/** Used for drawing the background of dialogs and windows.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param titleBarColor: Title color.
|
||||
\param drawTitleBar: True to enable title drawing.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area.
|
||||
\param checkClientArea: When set to non-null the function will not draw anything,
|
||||
but will instead return the clientArea which can be used for drawing by the calling window.
|
||||
That is the area without borders and without titlebar.
|
||||
\return Returns rect where it would be good to draw title bar text. This will
|
||||
work even when checkClientArea is set to a non-null value.*/
|
||||
virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
|
||||
bool drawTitleBar, video::SColor titleBarColor,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip,
|
||||
core::rect<s32>* checkClientArea)
|
||||
{
|
||||
return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
|
||||
rect, clip, checkClientArea);
|
||||
}
|
||||
|
||||
virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
|
||||
bool drawTitleBar, video::SColor titleBarColor,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip,
|
||||
core::rect<s32>* checkClientArea,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a standard 3d menu pane
|
||||
/** Used for drawing for menus and context menus.
|
||||
It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
|
||||
EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DMenuPane(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DMenuPane(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DMenuPane(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a standard 3d tool bar
|
||||
/** Used for drawing for toolbars and menus.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DToolBar(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColored3DToolBar(element, rect, clip);
|
||||
}
|
||||
|
||||
virtual void drawColored3DToolBar(IGUIElement* element,
|
||||
const core::rect<s32>& rect,
|
||||
const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a tab button
|
||||
/** Used for drawing for tab buttons on top of tabs.
|
||||
\param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param active: Specifies if the tab is currently active.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DTabButton(IGUIElement* element, bool active,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||
{
|
||||
drawColored3DTabButton(element, active, rect, clip, alignment);
|
||||
}
|
||||
|
||||
virtual void drawColored3DTabButton(IGUIElement* element, bool active,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a tab control body
|
||||
/** \param element: Pointer to the element which wishes to draw this. This parameter
|
||||
is usually not used by ISkin, but can be used for example by more complex
|
||||
implementations to find out how to draw the part exactly.
|
||||
\param border: Specifies if the border should be drawn.
|
||||
\param background: Specifies if the background should be drawn.
|
||||
\param rect: Defining area where to draw.
|
||||
\param clip: Clip area. */
|
||||
virtual void draw3DTabBody(IGUIElement* element, bool border, bool background,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
|
||||
{
|
||||
drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
|
||||
}
|
||||
|
||||
virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background,
|
||||
const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws an icon, usually from the skin's sprite bank
|
||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||
This parameter is usually not used by IGUISkin, but can be used for example
|
||||
by more complex implementations to find out how to draw the part exactly.
|
||||
\param icon: Specifies the icon to be drawn.
|
||||
\param position: The position to draw the icon
|
||||
\param starttime: The time at the start of the animation
|
||||
\param currenttime: The present time, used to calculate the frame number
|
||||
\param loop: Whether the animation should loop or not
|
||||
\param clip: Clip area. */
|
||||
virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||
const core::position2di position,
|
||||
u32 starttime=0, u32 currenttime=0,
|
||||
bool loop=false, const core::rect<s32>* clip=0)
|
||||
{
|
||||
drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
|
||||
}
|
||||
|
||||
virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
|
||||
const core::position2di position,
|
||||
u32 starttime=0, u32 currenttime=0,
|
||||
bool loop=false, const core::rect<s32>* clip=0,
|
||||
const video::SColor* colors=0);
|
||||
|
||||
//! draws a 2d rectangle.
|
||||
/** \param element: Pointer to the element which wishes to draw this icon.
|
||||
This parameter is usually not used by IGUISkin, but can be used for example
|
||||
by more complex implementations to find out how to draw the part exactly.
|
||||
\param color: Color of the rectangle to draw. The alpha component specifies how
|
||||
transparent the rectangle will be.
|
||||
\param pos: Position of the rectangle.
|
||||
\param clip: Pointer to rectangle against which the rectangle will be clipped.
|
||||
If the pointer is null, no clipping will be performed. */
|
||||
virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
|
||||
const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
|
||||
|
||||
|
||||
//! get the type of this skin
|
||||
virtual EGUI_SKIN_TYPE getType() const;
|
||||
|
||||
//! gets the colors
|
||||
virtual void getColors(video::SColor* colors); // ::PATCH:
|
||||
|
||||
private:
|
||||
|
||||
video::SColor Colors[EGDC_COUNT];
|
||||
s32 Sizes[EGDS_COUNT];
|
||||
u32 Icons[EGDI_COUNT];
|
||||
IGUIFont* Fonts[EGDF_COUNT];
|
||||
IGUISpriteBank* SpriteBank;
|
||||
core::stringw Texts[EGDT_COUNT];
|
||||
video::IVideoDriver* Driver;
|
||||
bool UseGradient;
|
||||
|
||||
EGUI_SKIN_TYPE Type;
|
||||
};
|
||||
|
||||
#define set3DSkinColors(skin, button_color) \
|
||||
{ \
|
||||
skin->setColor(EGDC_3D_FACE, button_color); \
|
||||
skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
|
||||
skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
|
||||
skin->setColor(EGDC_3D_LIGHT, button_color); \
|
||||
skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
|
||||
}
|
||||
|
||||
#define getElementSkinColor(color) \
|
||||
{ \
|
||||
if (!Colors) \
|
||||
{ \
|
||||
IGUISkin* skin = Environment->getSkin(); \
|
||||
if (skin) \
|
||||
return skin->getColor(color); \
|
||||
} \
|
||||
return Colors[color]; \
|
||||
}
|
||||
|
||||
#define setElementSkinColor(which, newColor, shading) \
|
||||
{ \
|
||||
if (!Colors) \
|
||||
{ \
|
||||
Colors = new video::SColor[EGDC_COUNT]; \
|
||||
GUISkin* skin = (GUISkin *)Environment->getSkin(); \
|
||||
if (skin) \
|
||||
skin->getColors(Colors); \
|
||||
} \
|
||||
Colors[which] = newColor; \
|
||||
setShading(Colors[which],shading); \
|
||||
}
|
||||
} // end namespace gui
|
||||
//! Sets the shading
|
||||
inline void setShading(video::SColor &color,f32 s) // :PATCH:
|
||||
{
|
||||
if (s < 1.0f)
|
||||
{
|
||||
color.setRed(color.getRed() * s);
|
||||
color.setGreen(color.getGreen() * s);
|
||||
color.setBlue(color.getBlue() * s);
|
||||
}
|
||||
else if (s > 1.0f)
|
||||
{
|
||||
s -= 1.0f;
|
||||
|
||||
color.setRed(color.getRed() + (255 - color.getRed()) * s);
|
||||
color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
|
||||
color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
|
||||
}
|
||||
}
|
||||
} // end namespace irr
|
||||
|
||||
#endif
|
||||
|
|
|
@ -239,7 +239,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
|
|||
#ifdef __ANDROID__
|
||||
// display software keyboard when clicking edit boxes
|
||||
if (event.EventType == EET_MOUSE_INPUT_EVENT &&
|
||||
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
|
||||
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN &&
|
||||
!porting::hasPhysicalKeyboardAndroid()) {
|
||||
gui::IGUIElement *hovered =
|
||||
Environment->getRootGUIElement()->getElementFromPoint(
|
||||
core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
|
||||
|
|
|
@ -414,6 +414,7 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver)
|
|||
}
|
||||
|
||||
m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold");
|
||||
m_long_tap_delay = g_settings->getU16("touch_long_tap_delay");
|
||||
m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
|
||||
m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1");
|
||||
m_screensize = m_device->getVideoDriver()->getScreenSize();
|
||||
|
@ -999,7 +1000,7 @@ void TouchScreenGUI::step(float dtime)
|
|||
if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
|
||||
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
|
||||
|
||||
if (delta > MIN_DIG_TIME_MS) {
|
||||
if (delta > m_long_tap_delay) {
|
||||
m_tap_state = TapState::LongTap;
|
||||
}
|
||||
}
|
||||
|
@ -1105,11 +1106,11 @@ void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode)
|
|||
// Since the pointed thing has already been determined when this function
|
||||
// is called, we cannot use this function to update the shootline.
|
||||
|
||||
sanity_check(mode != TouchInteractionMode_USER);
|
||||
u64 now = porting::getTimeMs();
|
||||
bool target_dig_pressed = false;
|
||||
bool target_place_pressed = false;
|
||||
|
||||
u64 now = porting::getTimeMs();
|
||||
|
||||
// If the meanings of short and long taps have been swapped, abort any ongoing
|
||||
// short taps because they would do something else than the player expected.
|
||||
// Long taps don't need this, they're adjusted to the swapped meanings instead.
|
||||
|
|
|
@ -79,7 +79,6 @@ typedef enum
|
|||
AHBB_Dir_Right_Left
|
||||
} autohide_button_bar_dir;
|
||||
|
||||
#define MIN_DIG_TIME_MS 500
|
||||
#define BUTTON_REPEAT_DELAY 0.2f
|
||||
#define SETTINGS_BAR_Y_OFFSET 5
|
||||
#define RARE_CONTROLS_BAR_Y_OFFSET 5
|
||||
|
@ -225,6 +224,7 @@ private:
|
|||
v2u32 m_screensize;
|
||||
s32 button_size;
|
||||
double m_touchscreen_threshold;
|
||||
u16 m_long_tap_delay;
|
||||
bool m_visible; // is the whole touch screen gui visible
|
||||
|
||||
std::unordered_map<u16, rect<s32>> m_hotbar_rects;
|
||||
|
|
|
@ -40,24 +40,37 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
TouchInteraction::TouchInteraction()
|
||||
{
|
||||
pointed_nothing = LONG_DIG_SHORT_PLACE;
|
||||
pointed_node = LONG_DIG_SHORT_PLACE;
|
||||
// Map punching to single tap by default.
|
||||
pointed_object = SHORT_DIG_LONG_PLACE;
|
||||
pointed_nothing = TouchInteractionMode_USER;
|
||||
pointed_node = TouchInteractionMode_USER;
|
||||
pointed_object = TouchInteractionMode_USER;
|
||||
}
|
||||
|
||||
TouchInteractionMode TouchInteraction::getMode(const PointedThing &pointed) const
|
||||
TouchInteractionMode TouchInteraction::getMode(PointedThingType pointed_type) const
|
||||
{
|
||||
switch (pointed.type) {
|
||||
TouchInteractionMode result;
|
||||
switch (pointed_type) {
|
||||
case POINTEDTHING_NOTHING:
|
||||
return pointed_nothing;
|
||||
result = pointed_nothing;
|
||||
break;
|
||||
case POINTEDTHING_NODE:
|
||||
return pointed_node;
|
||||
result = pointed_node;
|
||||
break;
|
||||
case POINTEDTHING_OBJECT:
|
||||
return pointed_object;
|
||||
result = pointed_object;
|
||||
break;
|
||||
default:
|
||||
FATAL_ERROR("Invalid PointedThingType given to TouchInteraction::getMode");
|
||||
}
|
||||
|
||||
if (result == TouchInteractionMode_USER) {
|
||||
if (pointed_type == POINTEDTHING_OBJECT)
|
||||
result = g_settings->get("touch_punch_gesture") == "long_tap" ?
|
||||
LONG_DIG_SHORT_PLACE : SHORT_DIG_LONG_PLACE;
|
||||
else
|
||||
result = LONG_DIG_SHORT_PLACE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TouchInteraction::serialize(std::ostream &os) const
|
||||
|
|
|
@ -30,10 +30,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "texture_override.h" // TextureOverride
|
||||
#include "tool.h"
|
||||
#include "util/pointabilities.h"
|
||||
#include "util/pointedthing.h"
|
||||
|
||||
class IGameDef;
|
||||
class Client;
|
||||
struct ToolCapabilities;
|
||||
struct PointedThing;
|
||||
#ifndef SERVER
|
||||
#include "client/texturesource.h"
|
||||
struct ItemMesh;
|
||||
|
@ -57,6 +58,7 @@ enum TouchInteractionMode : u8
|
|||
{
|
||||
LONG_DIG_SHORT_PLACE,
|
||||
SHORT_DIG_LONG_PLACE,
|
||||
TouchInteractionMode_USER, // Meaning depends on client-side settings
|
||||
TouchInteractionMode_END, // Dummy for validity check
|
||||
};
|
||||
|
||||
|
@ -67,7 +69,9 @@ struct TouchInteraction
|
|||
TouchInteractionMode pointed_object;
|
||||
|
||||
TouchInteraction();
|
||||
TouchInteractionMode getMode(const PointedThing &pointed) const;
|
||||
// Returns the right mode for the pointed thing and resolves any occurrence
|
||||
// of TouchInteractionMode_USER into an actual mode.
|
||||
TouchInteractionMode getMode(PointedThingType pointed_type) const;
|
||||
void serialize(std::ostream &os) const;
|
||||
void deSerialize(std::istream &is);
|
||||
};
|
||||
|
|
|
@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "util/numeric.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
|
|
@ -103,12 +103,10 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
|
|||
std::list<SharedBuffer<u8>> *chunks)
|
||||
{
|
||||
// Chunk packets, containing the TYPE_SPLIT header
|
||||
u32 chunk_header_size = 7;
|
||||
u32 maximum_data_size = chunksize_max - chunk_header_size;
|
||||
u32 start = 0;
|
||||
u32 end = 0;
|
||||
u32 chunk_num = 0;
|
||||
u16 chunk_count = 0;
|
||||
const u32 chunk_header_size = 7;
|
||||
const u32 maximum_data_size = chunksize_max - chunk_header_size;
|
||||
u32 start = 0, end = 0;
|
||||
u16 chunk_num = 0;
|
||||
do {
|
||||
end = start + maximum_data_size - 1;
|
||||
if (end > data.getSize() - 1)
|
||||
|
@ -126,16 +124,16 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
|
|||
memcpy(&chunk[chunk_header_size], &data[start], payload_size);
|
||||
|
||||
chunks->push_back(chunk);
|
||||
chunk_count++;
|
||||
|
||||
start = end + 1;
|
||||
sanity_check(chunk_num < 0xFFFF); // overflow
|
||||
chunk_num++;
|
||||
}
|
||||
while (end != data.getSize() - 1);
|
||||
|
||||
for (SharedBuffer<u8> &chunk : *chunks) {
|
||||
for (auto &chunk : *chunks) {
|
||||
// Write chunk_count
|
||||
writeU16(&(chunk[3]), chunk_count);
|
||||
writeU16(&chunk[3], chunk_num);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1061,22 +1059,22 @@ bool UDPPeer::processReliableSendCommand(
|
|||
const auto &c = *c_ptr;
|
||||
Channel &chan = channels[c.channelnum];
|
||||
|
||||
u32 chunksize_max = max_packet_size
|
||||
const u32 chunksize_max = max_packet_size
|
||||
- BASE_HEADER_SIZE
|
||||
- RELIABLE_HEADER_SIZE;
|
||||
|
||||
sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512);
|
||||
|
||||
std::list<SharedBuffer<u8>> originals;
|
||||
u16 split_sequence_number = chan.readNextSplitSeqNum();
|
||||
|
||||
if (c.raw) {
|
||||
originals.emplace_back(c.data);
|
||||
} else {
|
||||
makeAutoSplitPacket(c.data, chunksize_max,split_sequence_number, &originals);
|
||||
chan.setNextSplitSeqNum(split_sequence_number);
|
||||
u16 split_seqnum = chan.readNextSplitSeqNum();
|
||||
makeAutoSplitPacket(c.data, chunksize_max, split_seqnum, &originals);
|
||||
chan.setNextSplitSeqNum(split_seqnum);
|
||||
}
|
||||
|
||||
sanity_check(originals.size() < MAX_RELIABLE_WINDOW_SIZE);
|
||||
|
||||
bool have_sequence_number = false;
|
||||
bool have_initial_sequence_number = false;
|
||||
std::queue<BufferedPacketPtr> toadd;
|
||||
|
@ -1271,7 +1269,7 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout,
|
|||
m_udpSocket(ipv6),
|
||||
m_protocol_id(protocol_id),
|
||||
m_sendThread(new ConnectionSendThread(max_packet_size, timeout)),
|
||||
m_receiveThread(new ConnectionReceiveThread(max_packet_size)),
|
||||
m_receiveThread(new ConnectionReceiveThread()),
|
||||
m_bc_peerhandler(peerhandler)
|
||||
|
||||
{
|
||||
|
@ -1505,6 +1503,15 @@ void Connection::Send(session_t peer_id, u8 channelnum,
|
|||
{
|
||||
assert(channelnum < CHANNEL_COUNT); // Pre-condition
|
||||
|
||||
// approximate check similar to UDPPeer::processReliableSendCommand()
|
||||
// to get nicer errors / backtraces if this happens.
|
||||
if (reliable && pkt->getSize() > MAX_RELIABLE_WINDOW_SIZE*512) {
|
||||
std::ostringstream oss;
|
||||
oss << "Packet too big for window, peer_id=" << peer_id
|
||||
<< " command=" << pkt->getCommand() << " size=" << pkt->getSize();
|
||||
FATAL_ERROR(oss.str().c_str());
|
||||
}
|
||||
|
||||
putCommand(ConnectionCommand::send(peer_id, channelnum, pkt, reliable));
|
||||
}
|
||||
|
||||
|
|
|
@ -796,7 +796,7 @@ void ConnectionSendThread::sendAsPacket(session_t peer_id, u8 channelnum,
|
|||
m_outgoing_queue.push(packet);
|
||||
}
|
||||
|
||||
ConnectionReceiveThread::ConnectionReceiveThread(unsigned int max_packet_size) :
|
||||
ConnectionReceiveThread::ConnectionReceiveThread() :
|
||||
Thread("ConnectionReceive")
|
||||
{
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ private:
|
|||
class ConnectionReceiveThread : public Thread
|
||||
{
|
||||
public:
|
||||
ConnectionReceiveThread(unsigned int max_packet_size);
|
||||
ConnectionReceiveThread();
|
||||
|
||||
void *run();
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "inventory.h"
|
||||
#include "irrlicht_changes/printing.h"
|
||||
#include "log.h"
|
||||
#include "debug.h"
|
||||
#include "util/serialize.h"
|
||||
#include "constants.h" // MAP_BLOCKSIZE
|
||||
#include <sstream>
|
||||
|
|
|
@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "irrlicht_changes/printing.h"
|
||||
#include "irrlichttypes_bloated.h"
|
||||
#include "exceptions.h"
|
||||
#include "log.h"
|
||||
#include "util/serialize.h"
|
||||
#include <sstream>
|
||||
|
||||
|
|
|
@ -30,6 +30,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "log.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <jni.h>
|
||||
#define SDL_MAIN_HANDLED 1
|
||||
#include <SDL.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
|
@ -53,10 +57,8 @@ namespace porting {
|
|||
bool setSystemPaths(); // used in porting.cpp
|
||||
}
|
||||
|
||||
void android_main(android_app *app)
|
||||
extern "C" int SDL_Main(int _argc, char *_argv[])
|
||||
{
|
||||
porting::app_global = app;
|
||||
|
||||
Thread::setName("Main");
|
||||
|
||||
char *argv[] = {strdup(PROJECT_NAME), strdup("--verbose"), nullptr};
|
||||
|
@ -70,45 +72,15 @@ void android_main(android_app *app)
|
|||
}
|
||||
|
||||
namespace porting {
|
||||
android_app *app_global = nullptr;
|
||||
JNIEnv *jnienv = nullptr;
|
||||
jclass nativeActivity;
|
||||
|
||||
jclass findClass(const std::string &classname)
|
||||
{
|
||||
if (jnienv == nullptr)
|
||||
return nullptr;
|
||||
|
||||
jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity");
|
||||
jmethodID getClassLoader = jnienv->GetMethodID(
|
||||
nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
|
||||
jobject cls = jnienv->CallObjectMethod(
|
||||
app_global->activity->clazz, getClassLoader);
|
||||
jclass classLoader = jnienv->FindClass("java/lang/ClassLoader");
|
||||
jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
jstring strClassName = jnienv->NewStringUTF(classname.c_str());
|
||||
return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName);
|
||||
}
|
||||
jobject activity;
|
||||
jclass activityClass;
|
||||
|
||||
void osSpecificInit()
|
||||
{
|
||||
JavaVM *jvm = app_global->activity->vm;
|
||||
JavaVMAttachArgs lJavaVMAttachArgs;
|
||||
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
|
||||
lJavaVMAttachArgs.name = PROJECT_NAME_C "NativeThread";
|
||||
lJavaVMAttachArgs.group = nullptr;
|
||||
|
||||
if (jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) {
|
||||
errorstream << "Failed to attach native thread to jvm" << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
nativeActivity = findClass("net/minetest/minetest/GameActivity");
|
||||
if (nativeActivity == nullptr)
|
||||
errorstream <<
|
||||
"porting::initAndroid unable to find Java native activity class" <<
|
||||
std::endl;
|
||||
jnienv = (JNIEnv*)SDL_AndroidGetJNIEnv();
|
||||
activity = (jobject)SDL_AndroidGetActivity();
|
||||
activityClass = jnienv->GetObjectClass(activity);
|
||||
|
||||
// Set default language
|
||||
auto lang = getLanguageAndroid();
|
||||
|
@ -129,9 +101,6 @@ void cleanupAndroid()
|
|||
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
|
||||
moncleanup();
|
||||
#endif
|
||||
|
||||
JavaVM *jvm = app_global->activity->vm;
|
||||
jvm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
static std::string readJavaString(jstring j_str)
|
||||
|
@ -149,11 +118,11 @@ bool setSystemPaths()
|
|||
{
|
||||
// Set user and share paths
|
||||
{
|
||||
jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getUserDataPath = jnienv->GetMethodID(activityClass,
|
||||
"getUserDataPath", "()Ljava/lang/String;");
|
||||
FATAL_ERROR_IF(getUserDataPath==nullptr,
|
||||
"porting::initializePathsAndroid unable to find Java getUserDataPath method");
|
||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getUserDataPath);
|
||||
jobject result = jnienv->CallObjectMethod(activity, getUserDataPath);
|
||||
std::string str = readJavaString((jstring) result);
|
||||
path_user = str;
|
||||
path_share = str;
|
||||
|
@ -161,11 +130,11 @@ bool setSystemPaths()
|
|||
|
||||
// Set cache path
|
||||
{
|
||||
jmethodID getCachePath = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getCachePath = jnienv->GetMethodID(activityClass,
|
||||
"getCachePath", "()Ljava/lang/String;");
|
||||
FATAL_ERROR_IF(getCachePath==nullptr,
|
||||
"porting::initializePathsAndroid unable to find Java getCachePath method");
|
||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getCachePath);
|
||||
jobject result = jnienv->CallObjectMethod(activity, getCachePath);
|
||||
path_cache = readJavaString((jstring) result);
|
||||
}
|
||||
|
||||
|
@ -174,7 +143,7 @@ bool setSystemPaths()
|
|||
|
||||
void showTextInputDialog(const std::string &hint, const std::string ¤t, int editType)
|
||||
{
|
||||
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showTextInputDialog",
|
||||
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showTextInputDialog",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
|
||||
FATAL_ERROR_IF(showdialog == nullptr,
|
||||
|
@ -184,13 +153,13 @@ void showTextInputDialog(const std::string &hint, const std::string ¤t, in
|
|||
jstring jcurrent = jnienv->NewStringUTF(current.c_str());
|
||||
jint jeditType = editType;
|
||||
|
||||
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog,
|
||||
jnienv->CallVoidMethod(activity, showdialog,
|
||||
jhint, jcurrent, jeditType);
|
||||
}
|
||||
|
||||
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx)
|
||||
{
|
||||
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showSelectionInputDialog",
|
||||
jmethodID showdialog = jnienv->GetMethodID(activityClass, "showSelectionInputDialog",
|
||||
"([Ljava/lang/String;I)V");
|
||||
|
||||
FATAL_ERROR_IF(showdialog == nullptr,
|
||||
|
@ -205,79 +174,79 @@ void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 select
|
|||
jnienv->NewStringUTF(optionList[i].c_str()));
|
||||
}
|
||||
|
||||
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jOptionList,
|
||||
jnienv->CallVoidMethod(activity, showdialog, jOptionList,
|
||||
jselectedIdx);
|
||||
}
|
||||
|
||||
void openURIAndroid(const char *url)
|
||||
{
|
||||
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURI",
|
||||
jmethodID url_open = jnienv->GetMethodID(activityClass, "openURI",
|
||||
"(Ljava/lang/String;)V");
|
||||
|
||||
FATAL_ERROR_IF(url_open == nullptr,
|
||||
"porting::openURIAndroid unable to find Java openURI method");
|
||||
|
||||
jstring jurl = jnienv->NewStringUTF(url);
|
||||
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
|
||||
jnienv->CallVoidMethod(activity, url_open, jurl);
|
||||
}
|
||||
|
||||
void shareFileAndroid(const std::string &path)
|
||||
{
|
||||
jmethodID url_open = jnienv->GetMethodID(nativeActivity, "shareFile",
|
||||
jmethodID url_open = jnienv->GetMethodID(activityClass, "shareFile",
|
||||
"(Ljava/lang/String;)V");
|
||||
|
||||
FATAL_ERROR_IF(url_open == nullptr,
|
||||
"porting::shareFileAndroid unable to find Java shareFile method");
|
||||
|
||||
jstring jurl = jnienv->NewStringUTF(path.c_str());
|
||||
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
|
||||
jnienv->CallVoidMethod(activity, url_open, jurl);
|
||||
}
|
||||
|
||||
AndroidDialogType getLastInputDialogType()
|
||||
{
|
||||
jmethodID lastdialogtype = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID lastdialogtype = jnienv->GetMethodID(activityClass,
|
||||
"getLastDialogType", "()I");
|
||||
|
||||
FATAL_ERROR_IF(lastdialogtype == nullptr,
|
||||
"porting::getLastInputDialogType unable to find Java getLastDialogType method");
|
||||
|
||||
int dialogType = jnienv->CallIntMethod(app_global->activity->clazz, lastdialogtype);
|
||||
int dialogType = jnienv->CallIntMethod(activity, lastdialogtype);
|
||||
return static_cast<AndroidDialogType>(dialogType);
|
||||
}
|
||||
|
||||
AndroidDialogState getInputDialogState()
|
||||
{
|
||||
jmethodID inputdialogstate = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID inputdialogstate = jnienv->GetMethodID(activityClass,
|
||||
"getInputDialogState", "()I");
|
||||
|
||||
FATAL_ERROR_IF(inputdialogstate == nullptr,
|
||||
"porting::getInputDialogState unable to find Java getInputDialogState method");
|
||||
|
||||
int dialogState = jnienv->CallIntMethod(app_global->activity->clazz, inputdialogstate);
|
||||
int dialogState = jnienv->CallIntMethod(activity, inputdialogstate);
|
||||
return static_cast<AndroidDialogState>(dialogState);
|
||||
}
|
||||
|
||||
std::string getInputDialogMessage()
|
||||
{
|
||||
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID dialogvalue = jnienv->GetMethodID(activityClass,
|
||||
"getDialogMessage", "()Ljava/lang/String;");
|
||||
|
||||
FATAL_ERROR_IF(dialogvalue == nullptr,
|
||||
"porting::getInputDialogMessage unable to find Java getDialogMessage method");
|
||||
|
||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
|
||||
jobject result = jnienv->CallObjectMethod(activity,
|
||||
dialogvalue);
|
||||
return readJavaString((jstring) result);
|
||||
}
|
||||
|
||||
int getInputDialogSelection()
|
||||
{
|
||||
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, "getDialogSelection", "()I");
|
||||
jmethodID dialogvalue = jnienv->GetMethodID(activityClass, "getDialogSelection", "()I");
|
||||
|
||||
FATAL_ERROR_IF(dialogvalue == nullptr,
|
||||
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
|
||||
|
||||
return jnienv->CallIntMethod(app_global->activity->clazz, dialogvalue);
|
||||
return jnienv->CallIntMethod(activity, dialogvalue);
|
||||
}
|
||||
|
||||
#ifndef SERVER
|
||||
|
@ -287,13 +256,13 @@ float getDisplayDensity()
|
|||
static float value = 0;
|
||||
|
||||
if (firstrun) {
|
||||
jmethodID getDensity = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getDensity = jnienv->GetMethodID(activityClass,
|
||||
"getDensity", "()F");
|
||||
|
||||
FATAL_ERROR_IF(getDensity == nullptr,
|
||||
"porting::getDisplayDensity unable to find Java getDensity method");
|
||||
|
||||
value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity);
|
||||
value = jnienv->CallFloatMethod(activity, getDensity);
|
||||
firstrun = false;
|
||||
}
|
||||
|
||||
|
@ -306,22 +275,22 @@ v2u32 getDisplaySize()
|
|||
static v2u32 retval;
|
||||
|
||||
if (firstrun) {
|
||||
jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getDisplayWidth = jnienv->GetMethodID(activityClass,
|
||||
"getDisplayWidth", "()I");
|
||||
|
||||
FATAL_ERROR_IF(getDisplayWidth == nullptr,
|
||||
"porting::getDisplayWidth unable to find Java getDisplayWidth method");
|
||||
|
||||
retval.X = jnienv->CallIntMethod(app_global->activity->clazz,
|
||||
retval.X = jnienv->CallIntMethod(activity,
|
||||
getDisplayWidth);
|
||||
|
||||
jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getDisplayHeight = jnienv->GetMethodID(activityClass,
|
||||
"getDisplayHeight", "()I");
|
||||
|
||||
FATAL_ERROR_IF(getDisplayHeight == nullptr,
|
||||
"porting::getDisplayHeight unable to find Java getDisplayHeight method");
|
||||
|
||||
retval.Y = jnienv->CallIntMethod(app_global->activity->clazz,
|
||||
retval.Y = jnienv->CallIntMethod(activity,
|
||||
getDisplayHeight);
|
||||
|
||||
firstrun = false;
|
||||
|
@ -332,16 +301,29 @@ v2u32 getDisplaySize()
|
|||
|
||||
std::string getLanguageAndroid()
|
||||
{
|
||||
jmethodID getLanguage = jnienv->GetMethodID(nativeActivity,
|
||||
jmethodID getLanguage = jnienv->GetMethodID(activityClass,
|
||||
"getLanguage", "()Ljava/lang/String;");
|
||||
|
||||
FATAL_ERROR_IF(getLanguage == nullptr,
|
||||
"porting::getLanguageAndroid unable to find Java getLanguage method");
|
||||
|
||||
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
|
||||
jobject result = jnienv->CallObjectMethod(activity,
|
||||
getLanguage);
|
||||
return readJavaString((jstring) result);
|
||||
}
|
||||
|
||||
bool hasPhysicalKeyboardAndroid()
|
||||
{
|
||||
jmethodID hasPhysicalKeyboard = jnienv->GetMethodID(activityClass,
|
||||
"hasPhysicalKeyboard", "()Z");
|
||||
|
||||
FATAL_ERROR_IF(hasPhysicalKeyboard == nullptr,
|
||||
"porting::hasPhysicalKeyboardAndroid unable to find Java hasPhysicalKeyboard method");
|
||||
|
||||
jboolean result = jnienv->CallBooleanMethod(activity,
|
||||
hasPhysicalKeyboard);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // ndef SERVER
|
||||
}
|
||||
|
|
|
@ -23,21 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#error This header has to be included on Android port only!
|
||||
#endif
|
||||
|
||||
#include <jni.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include "irrlichttypes_bloated.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace porting {
|
||||
// Java app
|
||||
extern android_app *app_global;
|
||||
|
||||
// Java <-> C++ interaction interface
|
||||
extern JNIEnv *jnienv;
|
||||
|
||||
/**
|
||||
* Show a text input dialog in Java
|
||||
* @param hint Hint to be shown
|
||||
|
@ -105,6 +94,9 @@ std::string getInputDialogMessage();
|
|||
*/
|
||||
int getInputDialogSelection();
|
||||
|
||||
|
||||
bool hasPhysicalKeyboardAndroid();
|
||||
|
||||
#ifndef SERVER
|
||||
float getDisplayDensity();
|
||||
v2u32 getDisplaySize();
|
||||
|
|
|
@ -156,17 +156,24 @@ void read_item_definition(lua_State* L, int index,
|
|||
|
||||
getboolfield(L, index, "wallmounted_rotate_vertical", def.wallmounted_rotate_vertical);
|
||||
|
||||
TouchInteraction &inter = def.touch_interaction;
|
||||
lua_getfield(L, index, "touch_interaction");
|
||||
if (!lua_isnil(L, -1)) {
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
|
||||
TouchInteraction &inter = def.touch_interaction;
|
||||
if (lua_istable(L, -1)) {
|
||||
inter.pointed_nothing = (TouchInteractionMode)getenumfield(L, -1, "pointed_nothing",
|
||||
es_TouchInteractionMode, inter.pointed_nothing);
|
||||
inter.pointed_node = (TouchInteractionMode)getenumfield(L, -1, "pointed_node",
|
||||
es_TouchInteractionMode, inter.pointed_node);
|
||||
inter.pointed_object = (TouchInteractionMode)getenumfield(L, -1, "pointed_object",
|
||||
es_TouchInteractionMode, inter.pointed_object);
|
||||
} else if (lua_isstring(L, -1)) {
|
||||
int value;
|
||||
if (string_to_enum(es_TouchInteractionMode, value, lua_tostring(L, -1))) {
|
||||
inter.pointed_nothing = (TouchInteractionMode)value;
|
||||
inter.pointed_node = (TouchInteractionMode)value;
|
||||
inter.pointed_object = (TouchInteractionMode)value;
|
||||
}
|
||||
} else if (!lua_isnil(L, -1)) {
|
||||
throw LuaError("invalid type for 'touch_interaction'");
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ extern "C" {
|
|||
#include "util/numeric.h"
|
||||
#include "util/serialize.h"
|
||||
#include "util/string.h"
|
||||
#include "log.h"
|
||||
#include "common/c_converter.h"
|
||||
#include "common/c_internal.h"
|
||||
#include "constants.h"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue