12 Commits

Author SHA1 Message Date
Markil3
0fb384f1a9 Update README.md
Adds an instruction about changing the colors of the buttons.
2020-12-19 01:21:47 +00:00
Markil3
a42d7f4597 Update README.md
Updated the README for version 1.1.0.
2020-12-19 00:58:18 +00:00
Markil3
126a2e844d Adds support for Android 2020-12-18 17:14:16 -07:00
Markil3
8a6487f3bb Provides builds for both LWJGL2 and LWJGL3. 2020-12-18 12:31:25 -07:00
Markil3
d8726a4aa6 Migrated both screens to use the same API, merging both files into the same library. 2020-12-18 11:06:59 -07:00
Markil3
310356b1eb Moves many of the GUI tasks for CalibrateInputScreen to their own class.
I plan on reusing them in JoystickPreviewScreen.
2020-12-18 10:28:11 -07:00
Markil3
e2a8d1304b Removed the Lemur dependency (again).
Again, we want this to be as accessible to users as possible, and not everyone uses Lemur in their game.
2020-12-18 09:51:25 -07:00
Markil3
7ac6b8dd68 Adds a screen that prompts for the inputs needed for calibration.
We needed to add Lemur back in as a dependency in order to properly port this. We'll remove it again in a future commit.
2020-12-17 12:32:53 -07:00
Markil3
bd4242a79b Removes some repositories from the build.
We really didn't need those. Might as well keep it clean.
2020-12-16 09:08:40 -07:00
Markil3
19c57e53b7 Utilizes a new JoystickPreviewScreen that adds a "calibrate" button.
This will allow for extra buttons to be added to the demo application.
2020-12-15 22:50:07 -07:00
Markil3
c860efcfed Cleans up JoystickPreviewScreen a bit. 2020-12-15 22:44:41 -07:00
Markil3
aeef7b2e3f Update README.md 2020-12-14 20:31:53 +00:00
39 changed files with 2201 additions and 325 deletions

6
.gitignore vendored
View File

@@ -18,11 +18,15 @@
*.zip
*.tar.gz
*.rar
*.so
*.dll
/*/*.so
/*/*.dll
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
/local.properties
/build/
/library/build/
/*/build/
/.idea/
/.gradle/

View File

@@ -1,8 +1,16 @@
# JMEControllerConfig
A simple application for JMonkeyEngine-based games that allow for the inclusion of a gamepad view to monitor how exactly the JMonkeyEngine is viewing the gamepad. It can easilly be implemented into any JME3-based application without the need for external dependencies.
Derived from jMonkeyEngine's TestJoypad example, this is a simple utility library for JMonkeyEngine-based games that allow for the inclusion of in-game utilities to manage and configure game controllers in your game. The library can easily be implemented into any JME3-based application without the need for additional dependencies besides what you likely already have.
There are two screens included in the library. The JoystickPreviewScreen state will display the controllers currently connected to the computer (and update it as controllers are connected and disconnected on properly configured applications), as well as the values of all their inputs.
The CalibrateInputScreen state will show a series of prompts that will guide users through the process of creating a controller remapping properties file compatible with com.jme3.input.JoystickCompatibilityMappings.
## Getting started
Download the [latest release build](https://github.com/Markil3/JMEControllerConfig/releases) and store it in your project's library folder. Point your build tool to use the library. Then, ensure that your jMonkey application's settings (com.jme3.system.AppSettings#setUseJoysticks) are set to enable joypad support After that, just attach the markil3.controller.JoystickPreviewScreen to the jMonkey applications state manager to display the gamepad information. Simply detach the state to remove the information.
Download the [latest release build](https://github.com/Markil3/JMEControllerConfig/releases) and store it in your project's library folder. Point your build tool to use the library. Then, ensure that your jMonkey application's settings (com.jme3.system.AppSettings#setUseJoysticks) are set to enable joypad support After that, just attach the markil3.controller.JoystickPreviewScreen to the jMonkey applications state manager to display the gamepad information, or attach CalibrateInputScreen with a link to the output file to configure gamepads. Simply detach the state you used to remove the information.
If you need examples as to how to implement these, see the :desktop submodule.
If you wish to change the colors of the buttons, simply change the color values in the markil3.controller.GUIUtils class.
### Requirements
* Java 8+
@@ -13,6 +21,8 @@ Download the [latest release build](https://github.com/Markil3/JMEControllerConf
* "Interface/Joystick/gamepad-stick.png" from jme3-testdata
* jme3-desktop (if running as standalone)
* jme3-lwjgl3 or jme3-lwjgl (if running as standalone)
* jme3-android (if running on Android)
* jme3-android-natives (if running on Android)
* slf4j-api 1.7.15+
## Building from Source
@@ -31,15 +41,11 @@ This will create three .jar files in the library/build/libs directory, one binar
## Testing
If you want to test this without implementing it in your software, simply download the source as outlined above and run
<code>gradlew run</code>
<code>gradlew desktop:run</code>
This will run a bare-bones version dedicated to the utility.
This will run a bare-bones version dedicated to the utility using LWJGL3.
## FAQ
### Can I use this library in Adroid/iOS?
While I haven't fully tested it out, I foresee no problems. Just be sure to enable joysticks.
Note that on Android at least, the accelerometer is treated as gamepad 0.
Alternativly, you can use the :desktopLegacy subproject for LWJGL2, or the :android subproject for testing on Android.
## Troubleshooting
@@ -47,7 +53,7 @@ Note that on Android at least, the accelerometer is treated as gamepad 0.
There are two reasons that nothing would show up. First of all, the application state simply may not be attached properly to the state manager. Use a debugger to check this. Second, the application state may not detect any controllers in the com.jme3.input.InputManager#joysticks array. Ensure that your gamepads are connected and are registering in jMonkeyEngine.
### Every time I disconnect a controller, its entry is still present in the tab bar
This is a result of the controller disconnect callback being called before the controller is actually removed from the engine.
This is a result of the controller disconnect callback being called before the controller is actually removed from the engine. This is a limitation of the jMonkey API.
### My application crashes every time I attach it!
Be sure to enable joystick support in the application settings.
@@ -63,3 +69,12 @@ Be sure to enable joystick support in the application settings.
app.setSettings(settings);
app.start();
}
### Connecting/Pressing some buttons on Android causes crashes
Support for gamepads on Android appear to be a little spotty. This again is a limitation of the jmonkey engine. https://github.com/jMonkeyEngine/jmonkeyengine/issues/1448
### Some buttons show up under two mappings
This is a quirk with some gamepads. At the moment, this can interfere with the automapper. Workarounds are forthcoming.
### The triggers are always active during remapping
Many triggers are set to -1.0 in their default position. This can sometimes cause confusion to the automapper. Workarounds are forthcoming.

1
android/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

57
android/build.gradle Normal file
View File

@@ -0,0 +1,57 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
}
}
apply plugin: 'com.android.application'
repositories {
jcenter()
google()
}
android {
compileSdkVersion 29
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "markil3.controller"
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
api(project(":desktop")) {
exclude module: "jme3-lwjgl3"
exclude module: "jme3-desktop"
exclude module: "slf4j-simple"
}
implementation "uk.uuid.slf4j:slf4j-android:1.7.30-0"
implementation "${jme3.g}:jme3-android:${jme3.version}"
implementation "${jme3.g}:jme3-android-native:${jme3.version}"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.9"
}

21
android/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,28 @@
package markil3.controller;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest
{
@Test
public void useAppContext()
{
// Context of the app under test.
Context appContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("markil3.jmecontrollerconfig", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="markil3.jmecontrollerconfig">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.JMEControllerConfig">
<activity
android:name="markil3.controller.MainActivity"
android:configChanges="screenSize|orientation|screenLayout|navigation"
android:screenOrientation="sensorLandscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,73 @@
package markil3.controller;
import android.content.res.Configuration;
import android.os.Bundle;
import com.jme3.app.AndroidHarness;
import com.jme3.system.AppSettings;
import java.util.logging.Level;
import java.util.logging.LogManager;
public class MainActivity extends AndroidHarness
{
/*
* Note that you can ignore the errors displayed in this file, the android
* project will build regardless. Install the 'Android' plugin under
* Tools->Plugins->Available Plugins to get error checks and code completion
* for the Android project files.
*/
public MainActivity()
{
/*
* Set the default logging level (default=Level.INFO, Level.ALL=All
* Debug Info)
*/
LogManager.getLogManager().getLogger("").setLevel(Level.INFO);
this.appClass = Main.class.getName();
// Set the desired EGL configuration
eglBitsPerPixel = 24;
eglAlphaBits = 0;
eglDepthBits = 16;
eglSamples = 0;
eglStencilBits = 0;
// Set the maximum framerate
// (default = -1 for unlimited)
frameRate = -1;
// Set the maximum resolution dimension
// (the smaller side, height or width, is set automatically
// to maintain the original device screen aspect ratio)
// (default = -1 to match device screen resolution)
// maxResolutionDimension = -1;
// Set input configuration settings
joystickEventsEnabled = true;
keyEventsEnabled = true;
mouseEventsEnabled = true;
// Set application exit settings
finishOnAppStop = true;
handleExitHook = false;
exitDialogTitle = "Do you want to exit?";
exitDialogMessage =
"Use your home key to bring this app into the background " +
"or exit to terminate it.";
// Set splash screen resource id, if used
// (default = 0, no splash screen)
// For example, if the image file name is "splash"...
// splashPicID = R.drawable.splash;
splashPicID = 0;
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setImmersive(true);
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.JMEControllerConfig" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryDark">@color/purple_700</item>
<item name="colorAccent">@color/teal_200</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">JMEControllerConfig</string>
</resources>

View File

@@ -0,0 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.JMEControllerConfig" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryDark">@color/purple_700</item>
<item name="colorAccent">@color/teal_200</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,19 @@
package markil3.controller;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest
{
@Test
public void addition_isCorrect()
{
assertEquals(4, 2 + 2);
}
}

View File

@@ -8,115 +8,24 @@
* limitations under the License.
*/
import org.apache.tools.ant.taskdefs.condition.Os
ext {
jme3 = [version: '3.3.2-stable', g: 'org.jmonkeyengine']
versionNumber = 2
}
version = "1.1"
import java.nio.file.Files
task run {
doFirst {
print "WARNING: This project has two methods of execution: with LWJGL 2 and with LWJGL 3. Run the task desktopLegacy:run for the former and desktop:run for the latter."
}
}
buildscript {
allprojects {
repositories {
jcenter()
google()
}
}
plugins {
id "application"
id "distribution"
id "com.github.johnrengelman.shadow" version "5.0.0"
id "edu.sc.seis.launch4j" version "2.4.5"
id "edu.sc.seis.macAppBundle" version "2.3.0"
}
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
maven {
url "https://dl.bintray.com/jmonkeyengine/org.jmonkeyengine"
}
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
ext {
jme3 = [version: '3.3.2-stable', g: 'org.jmonkeyengine']
versionNumber = 3
}
dependencies {
// Logging
implementation "org.slf4j:slf4j-api:1.7.15"
implementation "org.slf4j:slf4j-simple:1.7.5"
implementation project(':library')
implementation "${jme3.g}:jme3-core:${jme3.version}"
implementation "${jme3.g}:jme3-desktop:${jme3.version}"
implementation "${jme3.g}:jme3-lwjgl3:${jme3.version}"
}
// Define the main class for the application
mainClassName = 'markil3.controller.Main'
version = "1.0"
versionNumber = 1
processResources {
from (".") {
include "LICENSE"
include "JME3-LICENSE"
}
}
/*
* TODO - The default zipped distributions are a bit of a mess.
*/
jar {
baseName rootProject.name
version rootProject.version
manifest {
attributes "Main-Class": mainClassName,
"Class-Path": configurations.runtime.files.collect {
"lib/$it.name"
}.join(" "),
"Implementation-Title": rootProject.name,
"Implementation-Version": rootProject.version,
"Version-Code": versionNumber
}
}
shadowJar {
archivesBaseName = rootProject.name
archiveClassifier = "universal"
version = rootProject.version
mainClassName = project.mainClassName
}
launch4j {
mainClassName = project.mainClassName
outfile = rootProject.name + "-" + project.version + "-windows.exe"
copyConfigurable = shadowJar.outputs.files
jar = shadowJar.getArchiveFile()
bundledJrePath = "jre"
//icon = new File(sourceSets.main.resources.srcDirs[0], "icon256.ico")
}
createExe.doLast({
println project.tasks.installDist.outputs.files.getClass()
for (file in new File(installDist.outputs.files[0], "lib").listFiles()) {
println file
}
})
macAppBundle {
mainClassName = project.mainClassName
icon = "myIcon.icns"
if (Os.isFamily(Os.FAMILY_MAC) && (appStyle == 'Oracle' || appStyle == 'universalJavaApplicationStub')) {
bundleJRE = true
}
javaProperties.put("apple.laf.useScreenMenuBar", "true")
backgroundImage = "doc/macbackground.png"
jarTask = "shadowJar"
}

86
desktop/build.gradle Normal file
View File

@@ -0,0 +1,86 @@
/*
* Copyright 2020 Markil 3. All rights reserved.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.tools.ant.taskdefs.condition.Os
plugins {
id "application"
id "distribution"
id "com.github.johnrengelman.shadow" version "5.0.0"
id "edu.sc.seis.launch4j" version "2.4.5"
id "edu.sc.seis.macAppBundle" version "2.3.0"
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
dependencies {
// Logging
implementation "org.slf4j:slf4j-api:1.7.15"
implementation "org.slf4j:slf4j-simple:1.7.5"
implementation project(':library')
implementation "${jme3.g}:jme3-core:${jme3.version}"
implementation "${jme3.g}:jme3-desktop:${jme3.version}"
implementation "${jme3.g}:jme3-lwjgl3:${jme3.version}"
}
// Define the main class for the application
mainClassName = 'markil3.controller.Main'
/*
* TODO - The default zipped distributions are a bit of a mess.
*/
jar {
baseName rootProject.name
version rootProject.version
manifest {
attributes "Main-Class": mainClassName,
"Class-Path": configurations.runtime.files.collect {
"lib/$it.name"
}.join(" "),
"Implementation-Title": rootProject.name,
"Implementation-Version": rootProject.version,
"Version-Code": versionNumber
}
}
shadowJar {
archivesBaseName = rootProject.name
archiveClassifier = "universal-lwjgl3"
version = rootProject.version
mainClassName = project.mainClassName
}
launch4j {
mainClassName = project.mainClassName
outfile = rootProject.name + "-" + rootProject.version + "-windows-lwjgl3.exe"
copyConfigurable = shadowJar.outputs.files
jar = shadowJar.archiveFile.get()
bundledJrePath = "jre"
//icon = new File(sourceSets.main.resources.srcDirs[0], "icon256.ico")
}
createExe.doLast({
println project.tasks.installDist.outputs.files.getClass()
for (file in new File(installDist.outputs.files[0], "lib").listFiles()) {
println file
}
})
macAppBundle {
mainClassName = project.mainClassName
icon = "myIcon.icns"
if (Os.isFamily(Os.FAMILY_MAC) && (appStyle == 'Oracle' || appStyle == 'universalJavaApplicationStub')) {
bundleJRE = true
}
javaProperties.put("apple.laf.useScreenMenuBar", "true")
backgroundImage = "doc/macbackground.png"
jarTask = "shadowJar"
}

View File

@@ -0,0 +1,276 @@
/*
* Copyright 2020 Markil 3. All rights reserved.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package markil3.controller;
import com.jme3.app.DebugKeysAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.font.BitmapText;
import com.jme3.input.JoystickCompatibilityMappings;
import com.jme3.input.controls.ActionListener;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* Launch point for the application. This is primarily for demo purposes, and
* games using this as a library can safely ignore it.
*
* @author Markil 3
* @version 1.0
*/
public class Main extends SimpleApplication implements ActionListener
{
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.
getLogger(Main.class);
public static File GAME_FOLDER;
public static File CALIBRATION_FILE;
public static File getGameFolder()
{
return GAME_FOLDER;
}
private static void initializeJoystickMappings()
{
if (CALIBRATION_FILE == null)
{
CALIBRATION_FILE =
new File(GAME_FOLDER, "controllerCalibration.properties");
URL mappingUrl;
switch (JmeSystem.getPlatform())
{
case Windows32:
case Windows64:
mappingUrl = Main.class.
getResource("/joystick-mapping.windows.properties");
break;
case MacOSX32:
case MacOSX64:
case MacOSX_PPC32:
case MacOSX_PPC64:
mappingUrl = Main.class.
getResource("/joystick-mapping.osx.properties");
break;
case Linux32:
case Linux64:
case Linux_ARM32:
case Linux_ARM64:
mappingUrl = Main.class.
getResource("/joystick-mapping.linux.properties");
break;
case Android_ARM5:
case Android_ARM6:
case Android_ARM7:
case Android_ARM8:
case Android_X86:
case Android_Other:
mappingUrl = Main.class.
getResource("/joystick-mapping.android.properties");
break;
case iOS_ARM:
case iOS_X86:
mappingUrl = Main.class.
getResource("/joystick-mapping.ios.properties");
break;
default:
mappingUrl = null;
}
if (mappingUrl != null)
{
try
{
JoystickCompatibilityMappings
.loadMappingProperties(mappingUrl);
}
catch (IOException e)
{
logger.error("Unable to load joystick mappings for " +
mappingUrl, e);
}
}
mappingUrl = Main.class.
getResource("/joystick-mapping." +
JmeSystem.getPlatform().toString().toLowerCase() +
".properties");
if (mappingUrl != null)
{
try
{
JoystickCompatibilityMappings
.loadMappingProperties(mappingUrl);
}
catch (IOException e)
{
logger.error("Unable to load joystick mappings for " +
mappingUrl, e);
}
}
if (CALIBRATION_FILE.isFile())
{
try
{
JoystickCompatibilityMappings.loadMappingProperties(
CALIBRATION_FILE.toURI().toURL());
}
catch (IOException e)
{
logger.error("Unable to load joystick mappings.", e);
}
}
}
}
public static void main(String[] args)
{
Main app;
AppSettings settings;
app = new Main();
settings = new AppSettings(true);
settings.setTitle("Joystick Preview");
settings.setUseJoysticks(true);
settings.setEmulateMouse(true);
settings.setVSync(true);
settings.setWidth(1280);
settings.setHeight(720);
app.setSettings(settings);
app.start();
}
private Node calibrateButton;
public Main()
{
super(new StatsAppState(), new DebugKeysAppState(),
new JoystickPreviewScreen());
}
@Override
public void initialize()
{
final File[] possibleGameDirs =
new File[]{new File(System.getProperty("user.dir")),
new File(System.getProperty("user.home")),
JmeSystem.getStorageFolder(
JmeSystem.StorageFolderType.External)};
int i, l;
if (GAME_FOLDER == null)
{
for (i = 0, l = possibleGameDirs.length; i < l; i++)
{
GAME_FOLDER = possibleGameDirs[i];
if (!GAME_FOLDER.isDirectory())
{
if (!GAME_FOLDER.mkdir())
{
logger.warn("Cannot make " + GAME_FOLDER);
continue;
}
}
if (!GAME_FOLDER.canWrite())
{
logger.warn("Cannot write to " + GAME_FOLDER);
continue;
}
break;
}
if (i == l)
{
throw new RuntimeException(
"Could not create game directory folder.");
}
}
/*
* Add custom joystick mappings before the input manager is loaded.
*/
initializeJoystickMappings();
super.initialize();
}
@Override
public void simpleInitApp()
{
this.calibrateButton =
GUIUtils.createButton(this.getAssetManager(), this.guiFont,
this.getContext().getTouchInput() != null, "calibrate",
"Calibrate Gamepad");
}
@Override
public void simpleUpdate(float tpf)
{
JoystickPreviewScreen screen =
this.getStateManager().getState(JoystickPreviewScreen.class);
if (this.calibrateButton.getParent() == null && screen != null)
{
this.guiNode.attachChild(this.calibrateButton);
this.calibrateButton.setLocalTranslation(
(this.getCamera().getWidth() -
((BitmapText) this.calibrateButton.getChild(1))
.getLineWidth() - 10) / 2F,
this.getCamera().getHeight(), 0);
this.inputManager.addListener(this, screen.CLICK_MAPPING);
}
else if (this.calibrateButton.getParent() != null && screen == null)
{
this.guiNode.detachChild(this.calibrateButton);
this.inputManager.removeListener(this);
}
}
@Override
public void onAction(String name, boolean isPressed, float tpf)
{
String buttonId;
JoystickPreviewScreen screen =
this.getStateManager().getState(JoystickPreviewScreen.class);
if (screen != null && name.equals(screen.CLICK_MAPPING))
{
buttonId = GUIUtils.handleButtonPress(this.calibrateButton,
this.getInputManager().getCursorPosition(), isPressed);
if (!isPressed && "calibrate".equals(buttonId))
{
this.getStateManager().detach(screen);
this.getStateManager()
.attach(new CalibrateInputScreen(CALIBRATION_FILE));
}
}
}
@Override
public void restart()
{
super.restart();
this.enqueue(() -> {
JoystickPreviewScreen previewScreen = this.getStateManager()
.getState(JoystickPreviewScreen.class);
CalibrateInputScreen calibrateScreen =
this.getStateManager().getState(CalibrateInputScreen.class);
if (previewScreen != null)
{
previewScreen.resize();
}
if (calibrateScreen != null)
{
calibrateScreen.resize();
}
this.calibrateButton.setLocalTranslation(
(this.getCamera().getWidth() -
((BitmapText) this.calibrateButton.getChild(1))
.getLineWidth() - 10) / 2F,
this.getCamera().getHeight(), 0);
});
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2020 Markil 3. All rights reserved.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.tools.ant.taskdefs.condition.Os
plugins {
id "application"
id "distribution"
id "com.github.johnrengelman.shadow" version "5.0.0"
id "edu.sc.seis.launch4j" version "2.4.5"
id "edu.sc.seis.macAppBundle" version "2.3.0"
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
dependencies {
implementation(project(':desktop')) {
exclude group: jme3.g, module: "jme3-lwjgl3"
}
implementation "${jme3.g}:jme3-lwjgl:${jme3.version}"
}
// Define the main class for the application
mainClassName = 'markil3.controller.Main'
/*
* TODO - The default zipped distributions are a bit of a mess.
*/
jar {
baseName rootProject.name
version rootProject.version
manifest {
attributes "Main-Class": mainClassName,
"Class-Path": configurations.runtime.files.collect {
"lib/$it.name"
}.join(" "),
"Implementation-Title": rootProject.name,
"Implementation-Version": rootProject.version,
"Version-Code": versionNumber
}
}
shadowJar {
archivesBaseName = rootProject.name
archiveClassifier = "universal-lwjgl2"
version = rootProject.version
mainClassName = project.mainClassName
}
launch4j {
mainClassName = project.mainClassName
outfile = rootProject.name + "-" + rootProject.version + "-windows-lwjgl2.exe"
copyConfigurable = shadowJar.outputs.files
jar = shadowJar.archiveFile.get()
bundledJrePath = "jre"
//icon = new File(sourceSets.main.resources.srcDirs[0], "icon256.ico")
}
createExe.doLast({
println project.tasks.installDist.outputs.files.getClass()
for (file in new File(installDist.outputs.files[0], "lib").listFiles()) {
println file
}
})
macAppBundle {
mainClassName = project.mainClassName
icon = "myIcon.icns"
if (Os.isFamily(Os.FAMILY_MAC) && (appStyle == 'Oracle' || appStyle == 'universalJavaApplicationStub')) {
bundleJRE = true
}
javaProperties.put("apple.laf.useScreenMenuBar", "true")
backgroundImage = "doc/macbackground.png"
jarTask = "shadowJar"
}

15
gradle.properties Normal file
View File

@@ -0,0 +1,15 @@
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Fri Dec 18 13:35:19 MST 2020
android.enableJetifier=true
android.useAndroidX=true

View File

@@ -8,31 +8,10 @@
* limitations under the License.
*/
import org.apache.tools.ant.taskdefs.condition.Os
import java.nio.file.Files
buildscript {
repositories {
jcenter()
google()
}
}
plugins {
id "java-library"
}
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
maven {
url "https://dl.bintray.com/jmonkeyengine/org.jmonkeyengine"
}
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
@@ -47,7 +26,7 @@ dependencies {
}
processResources {
from ("..") {
from(".") {
include "LICENSE"
include "JME3-LICENSE"
}

View File

@@ -0,0 +1,894 @@
package markil3.controller;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.input.Joystick;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickButton;
import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import static markil3.controller.JoystickPreviewScreen.ACTION_BOTTOM;
import static markil3.controller.JoystickPreviewScreen.ACTION_LEFT;
import static markil3.controller.JoystickPreviewScreen.ACTION_RIGHT;
import static markil3.controller.JoystickPreviewScreen.ACTION_TOP;
import static markil3.controller.JoystickPreviewScreen.DPAD_DOWN;
import static markil3.controller.JoystickPreviewScreen.DPAD_LEFT;
import static markil3.controller.JoystickPreviewScreen.DPAD_RIGHT;
import static markil3.controller.JoystickPreviewScreen.DPAD_UP;
import static markil3.controller.JoystickPreviewScreen.L1;
import static markil3.controller.JoystickPreviewScreen.L2;
import static markil3.controller.JoystickPreviewScreen.L3;
import static markil3.controller.JoystickPreviewScreen.R1;
import static markil3.controller.JoystickPreviewScreen.R2;
import static markil3.controller.JoystickPreviewScreen.R3;
import static markil3.controller.JoystickPreviewScreen.SELECT;
import static markil3.controller.JoystickPreviewScreen.START;
/**
* Provides a series of prompts that will build a controller calibration file.
* @author Markil3
* @version 1.1
*/
public class CalibrateInputScreen extends BaseAppState
implements RawInputListener, ActionListener
{
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(CalibrateInputScreen.class);
private static final LinkedHashMap<String, String> BUTTON_PROMPTS =
new LinkedHashMap<>();
private static final LinkedHashMap<String, String> AXIS_PROMPTS =
new LinkedHashMap<>();
final String CLICK_MAPPING = "calibrateButtonClick";
static
{
BUTTON_PROMPTS.put(ACTION_TOP, "Top Action Button");
BUTTON_PROMPTS.put(ACTION_RIGHT, "Right Action Button");
BUTTON_PROMPTS.put(ACTION_BOTTOM, "Bottom Action Button");
BUTTON_PROMPTS.put(ACTION_LEFT, "Left Action Button");
BUTTON_PROMPTS.put(L1, "Left Bumper (L1)");
BUTTON_PROMPTS.put(R1, "Right Bumper (R1)");
BUTTON_PROMPTS.put(L2, "Left Trigger (L2)");
BUTTON_PROMPTS.put(R2, "Right Trigger (R2)");
BUTTON_PROMPTS.put(SELECT, "Back/Select");
BUTTON_PROMPTS.put(START, "Start");
BUTTON_PROMPTS.put(L3, "Left Analog Stick Button (L3)");
BUTTON_PROMPTS.put(R3, "Right Analog Stick Button (R3)");
AXIS_PROMPTS.put(JoystickAxis.X_AXIS, "Left Analog Stick (Horizontal)");
AXIS_PROMPTS.put(JoystickAxis.Y_AXIS, "Left Analog Stick (Vertical)");
AXIS_PROMPTS
.put(JoystickAxis.Z_AXIS, "Right Analog Stick (Horizontal)");
AXIS_PROMPTS
.put(JoystickAxis.Z_ROTATION, "Right Analog Stick (Vertical)");
AXIS_PROMPTS.put(JoystickAxis.POV_X, "D-Pad (Horizontal)");
AXIS_PROMPTS.put(JoystickAxis.POV_Y, "D-Pad (Vertical)");
}
/**
* This enum is used to determine whether we are working with buttons or
* axes.
*/
private enum PromptStage
{
BUTTON(BUTTON_PROMPTS), AXIS(AXIS_PROMPTS);
private final HashMap<String, String> buttonNames;
PromptStage(HashMap<String, String> buttonNames)
{
this.buttonNames = buttonNames;
}
}
private final File calibrationFile;
private Node gui;
protected BitmapFont guiFont;
private Node introCont;
private Node startButton;
private Node mainOptions;
private Node skipButton;
private Node cancelButton;
private Node restartButton;
private JoystickPreviewScreen.GamepadView gamepad;
private BitmapText currentJoystick;
private BitmapText currentElement;
private BitmapText currentTime;
private boolean listeningRaw;
private Joystick joystick;
private Iterator<String> calibrationIter;
private String currentButton;
private boolean currentBias;
private PromptStage currentStage = null;
private Object focusedJoyElement;
private float timeHeld = -1;
private float focusValue;
/**
* Keeps track of whether the L2 and R2 functionalities are controlled by
* buttons or triggers.
*/
private boolean triggers2;
private HashMap<String, Object> maps = new HashMap<>();
private HashMap<String, Boolean> mapBias = new HashMap<>();
private HashMap<Object, Float> defaultValues = new HashMap<>();
/**
* Creates a screen for calibrating and remapping a game controller.
* @param calibrationFile - The file to store the results in.
*/
public CalibrateInputScreen(File calibrationFile)
{
this.calibrationFile = calibrationFile;
}
@Override
protected void initialize(Application app)
{
BitmapText text;
this.gui = new Node();
this.guiFont =
app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
this.introCont = new Node();
text = this.guiFont.createLabel(
"This screen will let you properly calibrate your " +
"controller\n" +
"inputs. When prompted, press the button or axis " +
"that\n" +
"corresponds to the highlighted button and hold it\n" +
"for one seconds. Do not apply any additional " +
"inputs\n" +
"beyond what is prompted. Press \"Start\" to \n" +
"begin.");
text.setBox(new Rectangle(0, 0, text.getLineWidth(), text.getHeight()));
text.setAlignment(BitmapFont.Align.Center);
text.setVerticalAlignment(BitmapFont.VAlign.Center);
this.introCont.attachChild(text);
this.startButton =
GUIUtils.createButton(app.getAssetManager(), this.guiFont, app.getContext().getTouchInput() != null,
"start", "Start");
// this.startButton.addClickCommands(this);
this.introCont.attachChild(this.startButton);
this.mainOptions = new Node();
this.mainOptions.attachChild(this.guiFont.createLabel(
"First, press any button or axis\n" +
"on the controller you want to calibrate."));
this.skipButton =
GUIUtils.createButton(app.getAssetManager(), this.guiFont, app.getContext().getTouchInput() != null,
"skip", "Skip");
this.cancelButton =
GUIUtils.createButton(app.getAssetManager(), this.guiFont, app.getContext().getTouchInput() != null,
"cancel", "Cancel");
this.introCont.attachChild(this.cancelButton);
this.restartButton =
GUIUtils.createButton(app.getAssetManager(), this.guiFont, app.getContext().getTouchInput() != null,
"close", "Close Application");
this.gui.attachChild(this.introCont);
this.gamepad = new JoystickPreviewScreen.GamepadView(null,
this.getApplication().getAssetManager());
((SimpleApplication) this.getApplication()).getGuiNode()
.attachChild(this.gui);
app.getInputManager().addMapping(CLICK_MAPPING,
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
}
@Override
public void update(float tpf)
{
super.update(tpf);
float MAX_HOLD_TIME = 1F;
super.update(tpf);
if (this.timeHeld >= 0)
{
this.timeHeld += tpf;
this.currentTime.setText(
(int) Math.ceil(MAX_HOLD_TIME - this.timeHeld) +
" s left...");
if (this.timeHeld >= MAX_HOLD_TIME)
{
switch (this.currentButton)
{
case L2:
if (this.focusedJoyElement instanceof JoystickAxis)
{
this.maps.put(JoystickAxis.LEFT_TRIGGER,
this.focusedJoyElement);
}
else
{
this.maps.
put(this.currentButton, this.focusedJoyElement);
}
break;
case R2:
if (this.focusedJoyElement instanceof JoystickAxis)
{
this.maps.put(JoystickAxis.RIGHT_TRIGGER,
this.focusedJoyElement);
}
else
{
this.maps.
put(this.currentButton, this.focusedJoyElement);
}
break;
case JoystickAxis.POV_X:
if (this.focusedJoyElement instanceof JoystickButton)
{
this.maps.put(this.focusValue > 0 ? DPAD_RIGHT :
DPAD_LEFT, this.focusedJoyElement);
}
else
{
this.maps.
put(this.currentButton, this.focusedJoyElement);
}
break;
case JoystickAxis.POV_Y:
if (this.focusedJoyElement instanceof JoystickButton)
{
this.maps.put(this.focusValue > 0 ? DPAD_UP : DPAD_DOWN,
this.focusedJoyElement);
}
else
{
this.maps.
put(this.currentButton, this.focusedJoyElement);
}
break;
default:
this.maps.put(this.currentButton, this.focusedJoyElement);
break;
}
this.mapBias.put(this.currentButton, this.focusValue > 0);
this.timeHeld = -1;
this.focusedJoyElement = null;
this.focusValue = 0;
if (this.currentStage == PromptStage.BUTTON)
{
this.calibrateNextButton();
}
else
{
this.calibrateNextAxis();
}
}
}
}
@Override
protected void cleanup(Application app)
{
app.getInputManager().deleteMapping(CLICK_MAPPING);
((SimpleApplication) this.getApplication()).getGuiNode()
.detachChild(this.gui);
}
@Override
protected void onEnable()
{
this.resize();
this.getApplication().getInputManager()
.addListener(this, CLICK_MAPPING);
}
@Override
protected void onDisable()
{
if (this.listeningRaw)
{
this.getApplication().getInputManager()
.removeRawInputListener(this);
}
this.getApplication().getInputManager().removeListener(this);
}
/**
* Automatically scales and positions elements based on the camera
* dimensions.
*/
public void resize()
{
Camera camera = this.getApplication().getCamera();
this.resize(camera.getWidth(), camera.getHeight());
}
/**
* Scales and positions elements of this screen.
* @param width - The width to scale to.
* @param height - The height to scale to.
*/
protected void resize(int width, int height)
{
float introHeight =
GUIUtils.alignContainer(this.introCont, width, height);
float mainHeight =
GUIUtils.alignContainer(this.mainOptions, width, height);
this.gui.setLocalTranslation(0, height / 2F, 0);
this.introCont.setLocalTranslation((width) / 2F, (introHeight) / 2F, 0);
this.mainOptions
.setLocalTranslation((width) / 4F, (mainHeight) / 2F, 0);
this.gamepad.setLocalTranslation(width / 2F, -256, 0);
if (this.currentJoystick != null)
{
this.currentJoystick.setLocalTranslation(
(width - this.currentJoystick.getLineWidth()) / 2F,
height / 2F, 0);
}
if (this.currentElement != null &&
this.currentElement.getParent() != null)
{
this.currentElement.setLocalTranslation(
width / 2F - this.currentElement.getLineWidth() - 10,
height / 2F - this.currentJoystick.getHeight(), 0);
}
if (this.currentTime != null && this.currentTime.getParent() != null)
{
this.currentTime.setLocalTranslation(width / 2F + 10,
height / 2F - this.currentJoystick.getHeight(), 0);
}
}
@Override
public void onAction(String name, boolean isPressed, float tpf)
{
String buttonId;
if (name.equals(CLICK_MAPPING))
{
buttonId = GUIUtils.handleButtonPress(this.gui,
this.getApplication().getInputManager().getCursorPosition(),
isPressed);
if (!isPressed && buttonId != null)
{
switch (buttonId)
{
case "start":
this.introCont.removeFromParent();
this.gui.attachChild(this.mainOptions);
this.gui.attachChild(this.gamepad);
this.resize();
this.listeningRaw = true;
this.getApplication().getInputManager()
.addRawInputListener(this);
break;
case "skip":
if (this.currentStage != null)
{
this.timeHeld = -1;
this.focusedJoyElement = null;
this.focusValue = 0;
if (this.currentStage == PromptStage.BUTTON)
{
this.calibrateNextButton();
}
else
{
this.calibrateNextAxis();
}
}
break;
case "cancel":
this.getStateManager().detach(this);
this.getStateManager().attach(new JoystickPreviewScreen());
break;
case "close":
this.getApplication().stop();
break;
}
}
}
}
/**
* Set which joystick to use for calibration. This change affects all
* joysticks sharing the same name, so you don't have to run this for
* every controller you have if some of them are identical.
* @param joystick - The joystick to use for calibration.
*/
private void setJoystick(Joystick joystick)
{
this.joystick = joystick;
this.currentJoystick =
this.guiFont.createLabel(this.joystick.getName());
this.currentJoystick
.setBox(new Rectangle(0, 0, this.currentJoystick.getLineWidth(),
this.currentJoystick.getHeight()));
this.currentJoystick.setAlignment(BitmapFont.Align.Center);
this.gui.attachChild(this.currentJoystick);
this.calibrationIter = BUTTON_PROMPTS.keySet().iterator();
this.currentElement = this.guiFont.createLabel("");
this.currentTime = this.guiFont.createLabel("");
this.currentStage = PromptStage.BUTTON;
calibrateNextButton();
}
/**
* Sets up the scene to calibrate the next button on the list. If we have
* gone through them all, we start on axis.
*/
private void calibrateNextButton()
{
if (this.currentButton != null)
{
this.gamepad.setButtonValue(this.currentButton, false);
}
this.currentElement.removeFromParent();
this.currentTime.removeFromParent();
if (this.calibrationIter.hasNext())
{
this.currentButton = this.calibrationIter.next();
if ((this.currentButton.equals(L2) ||
this.currentButton.equals(R2)) && this.triggers2)
{
this.calibrateNextButton();
return;
}
this.mainOptions.detachAllChildren();
this.mainOptions.attachChild(this.guiFont.createLabel("Press the"));
this.mainOptions.attachChild(this.guiFont
.createLabel(BUTTON_PROMPTS.get(this.currentButton)));
this.mainOptions.attachChild(this.skipButton);
this.mainOptions.attachChild(this.cancelButton);
this.gamepad.setButtonValue(this.currentButton, true);
this.resize();
}
else
{
this.currentBias = true;
this.currentButton = null;
this.calibrationIter = AXIS_PROMPTS.keySet().iterator();
this.currentStage = PromptStage.AXIS;
this.calibrateNextAxis();
}
}
/**
* Sets up the scene to calibrate the next button on the list. If we have
* gone through them all, we record the file.
*/
private void calibrateNextAxis()
{
// Clears the display
if (this.currentButton != null)
{
switch (this.currentButton)
{
case JoystickAxis.X_AXIS:
this.gamepad.setXAxis(0);
break;
case JoystickAxis.Y_AXIS:
this.gamepad.setYAxis(0);
break;
case JoystickAxis.Z_AXIS:
this.gamepad.setZAxis(0);
break;
case JoystickAxis.Z_ROTATION:
this.gamepad.setZRotation(0);
break;
case JoystickAxis.POV_X:
if (this.currentBias)
{
this.gamepad.
setButtonValue(DPAD_RIGHT, false);
}
else
{
this.gamepad.setButtonValue(DPAD_LEFT, false);
}
break;
case JoystickAxis.POV_Y:
if (this.currentBias)
{
this.gamepad.setButtonValue(DPAD_UP, false);
}
else
{
this.gamepad.setButtonValue(DPAD_DOWN, false);
}
break;
case JoystickAxis.LEFT_TRIGGER:
this.gamepad.setButtonValue(L2, false);
break;
case JoystickAxis.RIGHT_TRIGGER:
this.gamepad.setButtonValue(R2, false);
break;
}
}
this.currentElement.removeFromParent();
this.currentTime.removeFromParent();
// Increments the counter
if (!this.currentBias)
{
this.currentBias = true;
}
else if (this.calibrationIter.hasNext())
{
this.currentBias = false;
this.currentButton = this.calibrationIter.next();
if (this.currentButton.equals(JoystickAxis.LEFT_TRIGGER) ||
this.currentButton.equals(JoystickAxis.RIGHT_TRIGGER))
{
this.currentBias = true;
if (!this.triggers2)
{
this.calibrateNextAxis();
return;
}
}
}
else
{
this.currentButton = null;
}
if (this.currentButton != null)
{
this.mainOptions.detachAllChildren();
this.mainOptions.attachChild(this.guiFont.createLabel("Press the"));
this.mainOptions.attachChild(this.guiFont.createLabel(
(this.currentBias ? "Positive " : "Negative ") +
AXIS_PROMPTS.get(this.currentButton)));
this.mainOptions.attachChild(this.skipButton);
this.mainOptions.attachChild(this.cancelButton);
// Updates the display
switch (this.currentButton)
{
case JoystickAxis.X_AXIS:
this.gamepad.setXAxis(this.currentBias ? 1 : -1);
break;
case JoystickAxis.Y_AXIS:
this.gamepad.setYAxis(this.currentBias ? 1 : -1);
break;
case JoystickAxis.Z_AXIS:
this.gamepad.setZAxis(this.currentBias ? 1 : -1);
break;
case JoystickAxis.Z_ROTATION:
this.gamepad.setZRotation(this.currentBias ? 1 : -1);
break;
case JoystickAxis.POV_X:
if (this.currentBias)
{
this.gamepad.setButtonValue(DPAD_RIGHT, true);
}
else
{
this.gamepad.setButtonValue(DPAD_LEFT, true);
}
break;
case JoystickAxis.POV_Y:
if (this.currentBias)
{
this.gamepad.setButtonValue(DPAD_UP, true);
}
else
{
this.gamepad.setButtonValue(DPAD_DOWN, true);
}
break;
case JoystickAxis.LEFT_TRIGGER:
this.gamepad.setButtonValue(L2, true);
break;
case JoystickAxis.RIGHT_TRIGGER:
this.gamepad.setButtonValue(R2, true);
break;
}
this.resize();
}
else
{
this.recordFile();
}
}
/**
* Saves the calibration settings to the file and prompts for an
* application restart.
*/
private void recordFile()
{
/*
* Enable this in JME 3.4.
*/
boolean perComponentEnabled = false;
JoystickAxis axis;
JoystickButton button;
Properties props = new Properties();
this.currentStage = null;
this.mainOptions.removeFromParent();
this.gamepad.removeFromParent();
if (!calibrationFile.exists())
{
try
{
if (!calibrationFile.createNewFile())
{
throw new IOException("Could not create calibration file.");
}
}
catch (IOException ioe)
{
this.introCont.detachAllChildren();
this.introCont.attachChild(this.guiFont
.createLabel("Could not create Calibration File:"));
this.introCont.attachChild(this.cancelButton);
this.introCont.attachChild(
this.guiFont.createLabel("Error Details:"));
this.listStack(this.introCont, ioe, 0);
this.gui.attachChild(this.introCont);
this.resize();
}
}
try (FileInputStream input = new FileInputStream(calibrationFile))
{
props.load(input);
}
catch (IOException e)
{
logger.error("Could not load calibration file", e);
}
finally
{
for (Map.Entry<String, Object> calibrationEntry : this.maps
.entrySet())
{
if (calibrationEntry.getValue() instanceof JoystickButton)
{
button = (JoystickButton) calibrationEntry.getValue();
if (!button.getName().equals(calibrationEntry.getKey()))
{
props.put((perComponentEnabled ? "button." : "") +
this.joystick.getName() + "." +
button.getName(), calibrationEntry.getKey());
}
}
else if (calibrationEntry.getValue() instanceof JoystickAxis)
{
axis = (JoystickAxis) calibrationEntry.getValue();
if (!axis.getName().equals(calibrationEntry.getKey()))
{
props.put((perComponentEnabled ? "axis." : "") +
this.joystick.getName() + "." + axis.getName(),
calibrationEntry.getKey());
}
}
}
try (FileOutputStream output = new FileOutputStream(
calibrationFile))
{
props.store(output, "Joystick Calibration File");
this.introCont.detachAllChildren();
this.introCont.attachChild(this.guiFont
.createLabel("Calibration completed successfully."));
this.introCont.attachChild(this.guiFont.createLabel(
"Close the application and restart to load"));
this.introCont.attachChild(
this.guiFont.createLabel("the new settings."));
this.introCont.attachChild(this.restartButton);
this.gui.attachChild(this.introCont);
this.resize();
}
catch (IOException ioe)
{
this.introCont.detachAllChildren();
this.introCont.attachChild(this.guiFont
.createLabel("Could not create Calibration File:"));
this.introCont.attachChild(this.cancelButton);
this.introCont.attachChild(
this.guiFont.createLabel("Error Details:"));
this.listStack(this.introCont, ioe, 0);
this.gui.attachChild(this.introCont);
this.resize();
}
}
}
/**
* Display an error stack trace on the screen.
* @param errorCont - The container to use for the error.
* @param error - The error.
* @param depth - How many levels of exceptions throwing exceptions we
* are in. This is for recursively calling the method.
*/
private void listStack(Node errorCont, Throwable error, int depth)
{
StringBuilder errorBuilder = new StringBuilder();
Throwable cause = error;
while (cause != null)
{
errorBuilder.append(error.getClass().getName());
errorBuilder.append(": ");
errorBuilder.append(error.getMessage());
errorBuilder.append('\n');
for (StackTraceElement stack : error.getStackTrace())
{
errorBuilder.append('\t');
errorBuilder.append(stack.toString());
errorBuilder.append('\n');
}
errorBuilder.append('\n');
cause = error.getCause();
}
errorCont
.attachChild(this.guiFont.createLabel(errorBuilder.toString()));
}
@Override
public void onJoyAxisEvent(JoyAxisEvent evt)
{
// this.setJoystick(evt.getAxis().getJoystick());
if (this.joystick != null)
{
if (evt.getValue() != 0 && Math.abs(evt.getValue()) > 0.5 &&
(this.defaultValues.get(evt.getAxis()) == null || Math.abs(
this.defaultValues.get(evt.getAxis()) -
evt.getValue()) > 0.001F))
{
if (this.focusedJoyElement != evt.getAxis() ||
this.focusValue != evt.getValue())
{
this.focusedJoyElement = evt.getAxis();
this.timeHeld = 0;
this.focusValue = evt.getValue();
this.currentElement.setText(
(this.focusValue > 0 ? "+ " : "- ") +
evt.getAxis().getName());
this.gui.attachChild(this.currentElement);
this.gui.attachChild(this.currentTime);
this.resize();
}
}
else
{
if (this.focusedJoyElement == evt.getAxis())
{
this.focusedJoyElement = null;
this.timeHeld = -1;
this.focusValue = 0;
this.currentElement.removeFromParent();
this.currentTime.removeFromParent();
}
}
}
else
{
this.defaultValues.put(evt.getAxis(), evt.getValue());
}
}
@Override
public void onJoyButtonEvent(JoyButtonEvent evt)
{
if (this.joystick != null)
{
if (evt.isPressed() &&
(this.defaultValues.get(evt.getButton()) == null ||
Math.abs(this.defaultValues.get(evt.getButton()) -
1F) > 0.001F))
{
if (this.focusedJoyElement != evt.getButton() ||
this.focusValue != 1F)
{
this.focusedJoyElement = evt.getButton();
this.timeHeld = 0;
this.focusValue = 1;
this.currentElement.setText(evt.getButton().getName());
this.gui.attachChild(this.currentElement);
this.gui.attachChild(this.currentTime);
this.resize();
}
}
else
{
if (this.focusedJoyElement == evt.getButton())
{
this.focusedJoyElement = null;
this.timeHeld = -1;
this.focusValue = 0;
this.currentElement.removeFromParent();
this.currentTime.removeFromParent();
}
}
}
else
{
this.defaultValues
.put(evt.getButton(), evt.isPressed() ? 1.0F : 0.0F);
}
if (!evt.isPressed())
{
if (this.joystick == null)
{
this.setJoystick(evt.getButton().getJoystick());
}
}
}
@Override
public void beginInput()
{
}
@Override
public void endInput()
{
}
@Override
public void onMouseMotionEvent(MouseMotionEvent evt)
{
}
@Override
public void onMouseButtonEvent(MouseButtonEvent evt)
{
}
@Override
public void onKeyEvent(KeyInputEvent evt)
{
}
@Override
public void onTouchEvent(TouchEvent evt)
{
}
}

View File

@@ -0,0 +1,206 @@
package markil3.controller;
import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
/**
* A collection of utility methods for the screens used here.
* @author Markil 3
* @version 1.1
*/
public class GUIUtils
{
/**
* The color that a button takes on when the mouse is down on it. Feel
* free to change it to suit your needs.
*/
static ColorRGBA BUTTON_COLOR_ON = new ColorRGBA(0F, 0.1F, 0F, 1F);
/**
* The default color that a button. Feel free to change it to suit your
* needs.
*/
static ColorRGBA BUTTON_COLOR_OFF = new ColorRGBA(0F, 0.75F, 0.25F, 1F);
private static final String BUTTON_ID = "button";
/**
* Creates a button. This button will have special user data fields that
* can be detected by {@link #handleButtonPress(Node, Vector2f, boolean)}
* to trigger events.
* @param assets - The application asset manager.
* @param guiFont - The font to use for the button.
* @param isMobile - Whether or not the mobile size increase should be used.
* @param id - The button ID. This is used to identify which button was
* pressed later on.
* @param content - The text that will display in the button.
* @return A node containing the button elements.
*/
public static Node createButton(AssetManager assets, BitmapFont guiFont,
boolean isMobile, String id, String content)
{
final float MOBILE_SIZE = 30F;
BitmapText buttonText;
Geometry buttonBackground;
Node button = new Node();
buttonText = guiFont.createLabel(content);
if (isMobile)
{
buttonText.setSize(MOBILE_SIZE);
}
buttonText.setUserData(BUTTON_ID, id);
buttonText.setBox(new Rectangle(0, 0, buttonText.getLineWidth(),
buttonText.getLineHeight()));
buttonText.setAlignment(BitmapFont.Align.Center);
buttonText.setVerticalAlignment(BitmapFont.VAlign.Center);
buttonBackground = new Geometry("button-" + id,
new Quad(buttonText.getLineWidth() + 10,
buttonText.getHeight() + 5));
buttonBackground.setMaterial(
new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"));
buttonBackground.getMaterial().setColor("Color", BUTTON_COLOR_OFF);
buttonBackground.setUserData(BUTTON_ID, id);
buttonBackground
.setLocalTranslation(-2.5F, -buttonText.getLineHeight() - 2.5F,
0);
button.setUserData(BUTTON_ID, id);
button.attachChild(buttonBackground);
button.attachChild(buttonText);
return button;
}
/**
* Call this method from a mouse listener to search the provided node for
* any buttons created with
* {@link #createButton(AssetManager, BitmapFont, boolean, String, String)} and
* returns their ID.
* @param gui - The GUI to search.
* @param cursor - The position of the mouse cursor.
* @param isPressed - Whether or not the cursor is pressed. This will
* change how the button is displayed.
* @return The ID of the button that was clicked, or null if none were
* clicked.
*/
public static String handleButtonPress(Node gui, Vector2f cursor,
boolean isPressed)
{
final String KEY_CURRENT = "currentButton";
CollisionResults results;
Ray ray;
Geometry button;
String buttonId = null;
/*
* We generally mark buttons on mouse down, and trigger that same
* button on release. If we haven't even marked a button by the time
* we release the mouse, we just won't bother.
*/
if (!isPressed)
{
button = gui.getUserData(KEY_CURRENT);
if (button == null)
{
return null;
}
}
results = new CollisionResults();
ray = new Ray(new Vector3f(cursor.x, cursor.y, 0),
new Vector3f(0, 0, 1));
gui.collideWith(ray, results);
for (CollisionResult result : results)
{
button = result.getGeometry();
buttonId = button.getUserData(BUTTON_ID);
if (buttonId != null)
{
if (isPressed)
{
/*
* Trigger the press graphic
*/
button.getMaterial().setColor("Color", BUTTON_COLOR_ON);
gui.setUserData(KEY_CURRENT, button);
break;
}
else
{
/*
* If the button we released isn't the same as the button
* we originally pressed, don't trigger either.
*/
button = gui.getUserData(KEY_CURRENT);
if (button != null)
{
if (button.getUserData(BUTTON_ID).equals(buttonId))
{
break;
}
else
{
buttonId = null;
}
}
}
}
}
if (!isPressed)
{
/*
* Make sure the originally-clicked button was reset.
*/
button = gui.getUserData(KEY_CURRENT);
if (button != null)
{
button.getMaterial().setColor("Color", BUTTON_COLOR_OFF);
gui.setUserData(KEY_CURRENT, null);
}
}
return buttonId;
}
/**
* Aligns the contents in a container so that all {@link BitmapText} and
* button elements will align in a column.
* @param cont - The container to align.
* @param width - The width of the screen.
* @param height - The height of the screen.
* @return The total height of the container. This is useful in
* positioning the container itself.
*/
public static float alignContainer(Node cont, int width, int height)
{
float totalHeight = 0;
for (Spatial node : cont.getChildren())
{
if (node instanceof BitmapText)
{
node.setLocalTranslation(
-((BitmapText) node).getLineWidth() / 2F, -totalHeight,
0);
totalHeight += ((BitmapText) node).getHeight();
}
else if (node.getUserData(BUTTON_ID) != null)
{
Quad quad = ((Quad) ((Geometry) ((Node) node).getChild(0))
.getMesh());
node.setLocalTranslation(-quad.getWidth() / 2F,
-totalHeight - 10, 0);
totalHeight += quad.getHeight() + 10;
}
}
return totalHeight;
}
}

View File

@@ -14,15 +14,16 @@ import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.Joystick;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickButton;
import com.jme3.input.JoystickConnectionListener;
import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
@@ -33,9 +34,8 @@ import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
@@ -63,7 +63,7 @@ import java.util.Map;
* @author Stephen Gold
*/
public class JoystickPreviewScreen extends BaseAppState
implements RawInputListener, JoystickConnectionListener
implements RawInputListener, JoystickConnectionListener, ActionListener
{
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(JoystickPreviewScreen.class);
@@ -122,6 +122,7 @@ public class JoystickPreviewScreen extends BaseAppState
* Most gamepads may use {@link JoystickAxis#POV_Y} instead.
*/
public static final String DPAD_DOWN = "15";
final String CLICK_MAPPING = "previewButtonClick";
/**
* This node serves as the center of logic for each gamepad connected to
@@ -559,12 +560,8 @@ public class JoystickPreviewScreen extends BaseAppState
}
}
final ColorRGBA BUTTON_COLOR = new ColorRGBA(0F, 0.1F, 0F, 1F);
final ColorRGBA HIGHLIGHTED_BUTTON_COLOR =
new ColorRGBA(0F, 0.75F, 0.25F, 1F);
protected Node gui;
private BitmapFont guiFont;
protected BitmapFont guiFont;
private Node[] gamepadCont;
@@ -588,15 +585,11 @@ public class JoystickPreviewScreen extends BaseAppState
this.updateGamepad();
this.setEnabled(true);
((SimpleApplication) this.getApplication()).getGuiNode()
.attachChild(this.gui);
this.resize(((SimpleApplication) this.getApplication()).getCamera()
.getWidth(),
((SimpleApplication) this.getApplication()).getCamera()
.getHeight());
this.setEnabled(true);
app.getInputManager().addMapping(CLICK_MAPPING,
new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
}
/**
@@ -677,9 +670,6 @@ public class JoystickPreviewScreen extends BaseAppState
*/
private void addButtons()
{
Node button;
BitmapText buttonText;
Geometry buttonBackground;
int i, l;
/*
@@ -697,33 +687,13 @@ public class JoystickPreviewScreen extends BaseAppState
this.gamepadHeaders = new Node[l];
for (i = 0, l = this.gamepadCont.length; i < l; i++)
{
button = new Node();
buttonText = this.guiFont.createLabel("Gamepad " +
this.getApplication().getInputManager().getJoysticks()[i]
.getJoyId());
buttonText.setUserData("gamepad", i);
buttonBackground = new Geometry("gamepad" + i,
new Quad(buttonText.getLineWidth() + 10,
buttonText.getLineHeight() + 5));
buttonBackground.setMaterial(
new Material(this.getApplication().getAssetManager(),
"Common/MatDefs/Misc/Unshaded.j3md"));
if (this.gamepadCont[i].getParent() != null)
{
buttonBackground.getMaterial()
.setColor("Color", HIGHLIGHTED_BUTTON_COLOR);
}
else
{
buttonBackground.getMaterial().setColor("Color", BUTTON_COLOR);
}
buttonBackground.setUserData("gamepad", i);
buttonBackground
.setLocalTranslation(0, -buttonText.getLineHeight(), 0);
button.attachChild(buttonBackground);
button.attachChild(buttonText);
this.gui.attachChild(button);
this.gamepadHeaders[i] = button;
this.gamepadHeaders[i] = GUIUtils.createButton(
this.getApplication().getAssetManager(), this.guiFont,
this.getApplication().getContext().getTouchInput() != null,
"gamepad" + i, "Gamepad " +
this.getApplication().getInputManager()
.getJoysticks()[i].getJoyId());
this.gui.attachChild(this.gamepadHeaders[i]);
}
}
@@ -742,6 +712,7 @@ public class JoystickPreviewScreen extends BaseAppState
protected void cleanup(Application app)
{
this.gui.removeFromParent();
app.getInputManager().deleteMapping(CLICK_MAPPING);
}
@Override
@@ -750,6 +721,9 @@ public class JoystickPreviewScreen extends BaseAppState
this.getApplication().getInputManager().addRawInputListener(this);
this.getApplication().getInputManager()
.addJoystickConnectionListener(this);
this.resize();
this.getApplication().getInputManager()
.addListener(this, CLICK_MAPPING);
}
@Override
@@ -758,6 +732,7 @@ public class JoystickPreviewScreen extends BaseAppState
this.getApplication().getInputManager().removeRawInputListener(this);
this.getApplication().getInputManager()
.removeJoystickConnectionListener(this);
this.getApplication().getInputManager().removeListener(this);
}
/**
@@ -767,6 +742,7 @@ public class JoystickPreviewScreen extends BaseAppState
*/
private void setLabels(Joystick joy)
{
int offset = this.getApplication().getContext().getTouchInput() != null ? 10: 0;
/*
* Removes the old labels.
*/
@@ -787,19 +763,24 @@ public class JoystickPreviewScreen extends BaseAppState
/*
* The name of the gamepad.
*/
// this.labels[joy.getJoyId()][0] = this.guiFont.createLabel(
// "Gamepad " + joy.getJoyId() + ": " + joy.getName());
this.labels[joy.getJoyId()][0] =
this.guiFont.createLabel(joy.getName());
this.labels[joy.getJoyId()][0].setLocalTranslation(20, -25, 0);
this.labels[joy.getJoyId()][0].setLocalTranslation(20, -25 - offset, 0);
this.gamepadCont[joy.getJoyId()]
.attachChild(this.labels[joy.getJoyId()][0]);
/*
* The key for the axis. Each of the axis rows will display the
* index of the axis, its given name, its logical ID after
* joystick remapping has occurred, and the axis index again.
* TODO - Whenever I add the " Gamepad X: " part to the previous
* label, this one displays all funky. Also, the lag gets really
* bad.
*/
this.labels[joy.getJoyId()][1] = this.guiFont
.createLabel("Axis Index: Axis Name (logical ID, axis ID)");
this.labels[joy.getJoyId()][1].setLocalTranslation(20, -50, 0);
this.labels[joy.getJoyId()][1].setLocalTranslation(20, -50 - offset, 0);
this.gamepadCont[joy.getJoyId()]
.attachChild(this.labels[joy.getJoyId()][1]);
/*
@@ -817,7 +798,7 @@ public class JoystickPreviewScreen extends BaseAppState
(i) + ": " + axis.getName() + " (" +
axis.getLogicalId() + ", " +
axis.getAxisId() + "): ");
label.setLocalTranslation(20, -25 * (i + 3), 0);
label.setLocalTranslation(20, -25 * (i + 3) - offset, 0);
this.labels[joy.getJoyId()][i * 2 + 1] = label;
/*
@@ -859,7 +840,7 @@ public class JoystickPreviewScreen extends BaseAppState
this.labels[joy.getJoyId()][firstButtonIndex].setLocalTranslation(
this.getScreenSize().x -
this.labels[joy.getJoyId()][firstButtonIndex]
.getLineWidth(), -50, 0);
.getLineWidth(), -50 - offset, 0);
this.gamepadCont[joy.getJoyId()]
.attachChild(this.labels[joy.getJoyId()][firstButtonIndex]);
@@ -879,7 +860,7 @@ public class JoystickPreviewScreen extends BaseAppState
BitmapText label2 = this.guiFont.createLabel("false");
label2.setLocalTranslation(
this.getScreenSize().x - label2.getLineWidth(),
-25 * (i + 3), 0);
-25 * (i + 3) - offset, 0);
/*
* The name and information for the button.
@@ -915,11 +896,21 @@ public class JoystickPreviewScreen extends BaseAppState
}
/**
* Repositions the screen elements.
* @param width - The width of the screen.
* @param height - The height of the screen.
* Automatically scales and positions elements based on the camera
* dimensions.
*/
public void resize(int width, int height)
public void resize()
{
Camera camera = this.getApplication().getCamera();
this.resize(camera.getWidth(), camera.getHeight());
}
/**
* Scales and positions elements of this screen.
* @param width - The width to scale to.
* @param height - The height to scale to.
*/
protected void resize(int width, int height)
{
this.gui.setLocalTranslation(0, this.getScreenSize().y, 0);
if (this.gamepadHeaders != null)
@@ -928,7 +919,7 @@ public class JoystickPreviewScreen extends BaseAppState
for (int i = 0, l = this.gamepadHeaders.length; i < l; i++)
{
button = this.gamepadHeaders[i];
button.setLocalTranslation(128 * i, 0, 0);
button.setLocalTranslation((this.getApplication().getContext().getTouchInput() != null ? 192 : 128) * i, 0, 0);
}
}
if (this.gamepadView != null)
@@ -992,18 +983,35 @@ public class JoystickPreviewScreen extends BaseAppState
{
}
/**
* A convenience method to discover what the mouse is currently over.
* @param x - The current X coordinate of the mouse.
* @param y - The current Y coordinate of the mouse.
* @return Collision results on the GUI.
*/
private CollisionResults checkMouse(int x, int y)
@Override
public void onAction(String name, boolean isPressed, float tpf)
{
CollisionResults results = new CollisionResults();
Ray ray = new Ray(new Vector3f(x, y, 0), new Vector3f(x, y, 1));
this.gui.collideWith(ray, results);
return results;
String buttonId;
int gamepad;
if (name.equals(CLICK_MAPPING))
{
buttonId = GUIUtils.handleButtonPress(this.gui,
this.getApplication().getInputManager().getCursorPosition(),
isPressed);
if (!isPressed && buttonId != null)
{
if (buttonId.startsWith("gamepad"))
{
gamepad = Integer.parseInt(buttonId.substring(7));
for (int i = 0, l = this.gamepadCont.length; i < l; i++)
{
if (i == gamepad)
{
this.gui.attachChild(this.gamepadCont[i]);
}
else
{
this.gamepadCont[i].removeFromParent();
}
}
}
}
}
}
/**
@@ -1014,36 +1022,35 @@ public class JoystickPreviewScreen extends BaseAppState
@Override
public void onMouseMotionEvent(MouseMotionEvent evt)
{
ButtonView button = null;
CollisionResults results = this.checkMouse(evt.getX(), evt.getY());
if (results.size() > 0)
{
for (CollisionResult result : results)
{
if (result.getGeometry().getUserData("view") != null)
{
if (result.getGeometry()
.getUserData("view") instanceof ButtonView)
{
button = result.getGeometry().getUserData("view");
break;
}
}
}
}
if (button != null)
{
this.refLabel.setText(button.getName().substring(8));
Vector2f size = this.getScreenSize();
this.resize((int) size.x, (int) size.y);
}
else
{
if (this.refLabel.getText().length() > 0)
{
this.refLabel.setText("");
}
}
// ButtonView button = null;
// CollisionResults results = this.checkMouse(evt.getX(), evt.getY());
// if (results.size() > 0)
// {
// for (CollisionResult result : results)
// {
// if (result.getGeometry().getUserData("view") != null)
// {
// if (result.getGeometry()
// .getUserData("view") instanceof ButtonView)
// {
// button = result.getGeometry().getUserData("view");
// break;
// }
// }
// }
// }
// if (button != null)
// {
// this.refLabel.setText(button.getName().substring(8));
// this.resize();
// }
// else
// {
// if (this.refLabel.getText().length() > 0)
// {
// this.refLabel.setText("");
// }
// }
}
/**
@@ -1053,39 +1060,6 @@ public class JoystickPreviewScreen extends BaseAppState
@Override
public void onMouseButtonEvent(MouseButtonEvent evt)
{
Integer gamepad;
if (evt.isPressed() && evt.getButtonIndex() == 0)
{
CollisionResults results = this.checkMouse(evt.getX(), evt.getY());
if (results.size() > 0)
{
for (CollisionResult result : results)
{
if (result.getGeometry().getUserData("gamepad") != null)
{
gamepad = result.getGeometry().getUserData("gamepad");
for (int i = 0, l = this.gamepadCont.length; i < l; i++)
{
if (i == gamepad)
{
this.gui.attachChild(this.gamepadCont[i]);
((Geometry) this.gamepadHeaders[i].getChild(0))
.getMaterial().setColor("Color",
HIGHLIGHTED_BUTTON_COLOR);
}
else
{
this.gamepadCont[i].removeFromParent();
((Geometry) this.gamepadHeaders[i].getChild(0))
.getMaterial()
.setColor("Color", BUTTON_COLOR);
}
}
break;
}
}
}
}
}
@Override
@@ -1101,15 +1075,13 @@ public class JoystickPreviewScreen extends BaseAppState
@Override
public void onConnected(Joystick joystick)
{
Vector2f size = this.getScreenSize();
this.updateGamepad();
this.resize((int) size.x, (int) size.y);
this.resize();
}
@Override
public void onDisconnected(Joystick joystick)
{
Vector2f size = this.getScreenSize();
/*
* TODO - This callback will fire before the joystick is actually
* removed. This is helpful at times, but it does mean we will have a
@@ -1117,6 +1089,6 @@ public class JoystickPreviewScreen extends BaseAppState
* removed.
*/
this.updateGamepad();
this.resize((int) size.x, (int) size.y);
this.resize();
}
}

View File

@@ -7,6 +7,7 @@
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user guide at https://docs.gradle.org/4.4.1/userguide/multi_project_builds.html
*/
include ':jmecontrollerconfig'
/*
// To declare projects as part of a multi-project build use the 'include' method
@@ -17,3 +18,6 @@ include 'services:webservice'
rootProject.name = 'JMEControllerConfig'
include "library"
include "desktop"
include "desktopLegacy"
include "android"

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2020 Markil 3. All rights reserved.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package markil3.controller;
import com.jme3.app.DebugKeysAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.input.JoystickAxis;
import com.jme3.input.JoystickButton;
import com.jme3.system.AppSettings;
/**
* Launch point for the application. This is primarily for demo purposes, and
* games using this as a library can safely ignore it.
*
* @author Markil 3
* @version 1.0
*/
public class Main extends SimpleApplication
{
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.
getLogger(Main.class);
public static void main(String[] args)
{
Main app = new Main();
AppSettings settings = new AppSettings(true);
settings.setTitle("Joystick Preview");
settings.setUseJoysticks(true);
settings.setEmulateMouse(true);
settings.setVSync(true);
app.setSettings(settings);
app.start();
}
public Main()
{
super(new StatsAppState(), new DebugKeysAppState(), new JoystickPreviewScreen());
}
@Override
public void simpleInitApp()
{
}
}