From 6083fe5aec47e5dd46a22f62023ab6e36905ba7a Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Fri, 15 May 2026 15:41:42 +0200 Subject: [PATCH 01/12] ios backend --- build.gradle | 1 + .../src/main/java/com/jme3/renderer/Caps.java | 5 + .../com/jme3/renderer/opengl/GLRenderer.java | 6 +- .../com/jme3/util/MaterialDebugAppState.java | 13 +- .../jme3test/light/pbr/TestPBRSimple.java | 2 +- jme3-ios-examples/build.gradle | 385 +++ .../java/jme3test/ios/IosTestChooser.java | 428 +++ .../jme3test/ios/IosTestChooserLauncher.java | 205 ++ .../java/jme3test/ios/IosTestChooserTest.java | 176 ++ jme3-ios-native/build.gradle | 37 - jme3-ios-native/export.sh | 11 - .../jme3-ios-native.xcodeproj/project.pbxproj | 416 --- jme3-ios-native/src/Info.plist | 22 - jme3-ios-native/src/JmeAppHarness.java | 130 - jme3-ios-native/src/JmeAppHarness.m | 60 - jme3-ios-native/src/JmeIosGLES.m | 2392 ----------------- .../src/com_jme3_audio_ios_IosAL.c | 138 - .../src/com_jme3_audio_ios_IosAL.h | 173 -- .../src/com_jme3_audio_ios_IosALC.c | 178 -- .../src/com_jme3_audio_ios_IosALC.h | 77 - .../src/com_jme3_audio_ios_IosEFX.c | 79 - .../src/com_jme3_audio_ios_IosEFX.h | 101 - .../com_jme3_util_IosNativeBufferAllocator.c | 94 - .../com_jme3_util_IosNativeBufferAllocator.h | 29 - jme3-ios-native/src/jme-ios.m | 192 -- jme3-ios-native/src/jme3_ios_native.h | 18 - .../template/META-INF/robovm/ios/robovm.xml | 9 - jme3-ios/build.gradle | 24 + jme3-ios/src/main/java/com/jme3/asset/IOS.cfg | 10 - .../main/java/com/jme3/audio/ios/IosAL.java | 184 +- .../main/java/com/jme3/audio/ios/IosALC.java | 42 +- .../main/java/com/jme3/audio/ios/IosEFX.java | 48 +- .../com/jme3/input/ios/IosInputHandler.java | 136 +- .../java/com/jme3/input/ios/IosJoyInput.java | 345 +++ .../java/com/jme3/input/ios/IosSdlKeyMap.java | 347 +++ .../com/jme3/input/ios/IosTouchHandler.java | 62 +- .../com/jme3/renderer/ios/JmeIosGLES.java | 353 +-- .../com/jme3/system/ios/IGLESContext.java | 291 +- .../java/com/jme3/system/ios/IosHarness.java | 61 - ...IosImageLoader.java => JmeIosBackend.java} | 62 +- .../com/jme3/system/ios/JmeIosSystem.java | 26 +- .../com/jme3/system/ios/ObjcNativeObject.java | 130 - .../jme3/util/IosNativeBufferAllocator.java | 71 - .../util/LibJGLIOSNativeBufferAllocator.java | 27 + .../src/main/resources/com/jme3/asset/IOS.cfg | 7 - .../jme3/input/ios/IosInputHandlerTest.java | 220 ++ .../jme3/input/lwjgl/SdlJoystickInput.java | 25 + settings.gradle | 18 +- 48 files changed, 3074 insertions(+), 4792 deletions(-) create mode 100644 jme3-ios-examples/build.gradle create mode 100644 jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java create mode 100644 jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooserLauncher.java create mode 100644 jme3-ios-examples/src/test/java/jme3test/ios/IosTestChooserTest.java delete mode 100644 jme3-ios-native/build.gradle delete mode 100755 jme3-ios-native/export.sh delete mode 100644 jme3-ios-native/jme3-ios-native.xcodeproj/project.pbxproj delete mode 100644 jme3-ios-native/src/Info.plist delete mode 100644 jme3-ios-native/src/JmeAppHarness.java delete mode 100644 jme3-ios-native/src/JmeAppHarness.m delete mode 100644 jme3-ios-native/src/JmeIosGLES.m delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosAL.c delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosAL.h delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosALC.c delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosALC.h delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosEFX.c delete mode 100644 jme3-ios-native/src/com_jme3_audio_ios_IosEFX.h delete mode 100644 jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.c delete mode 100644 jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.h delete mode 100644 jme3-ios-native/src/jme-ios.m delete mode 100644 jme3-ios-native/src/jme3_ios_native.h delete mode 100644 jme3-ios-native/template/META-INF/robovm/ios/robovm.xml delete mode 100644 jme3-ios/src/main/java/com/jme3/asset/IOS.cfg create mode 100644 jme3-ios/src/main/java/com/jme3/input/ios/IosJoyInput.java create mode 100644 jme3-ios/src/main/java/com/jme3/input/ios/IosSdlKeyMap.java delete mode 100644 jme3-ios/src/main/java/com/jme3/system/ios/IosHarness.java rename jme3-ios/src/main/java/com/jme3/system/ios/{IosImageLoader.java => JmeIosBackend.java} (55%) delete mode 100644 jme3-ios/src/main/java/com/jme3/system/ios/ObjcNativeObject.java delete mode 100644 jme3-ios/src/main/java/com/jme3/util/IosNativeBufferAllocator.java create mode 100644 jme3-ios/src/main/java/com/jme3/util/LibJGLIOSNativeBufferAllocator.java create mode 100644 jme3-ios/src/test/java/com/jme3/input/ios/IosInputHandlerTest.java diff --git a/build.gradle b/build.gradle index 3a1395207c..4f87de2333 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ apply from: file('version.gradle') allprojects { repositories { + mavenLocal() mavenCentral() google() } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index dfe8a10669..1183002a65 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -427,6 +427,11 @@ public enum Caps { */ DepthTexture, + /** + * Supports hardware depth texture comparison for shadow maps. + */ + TextureShadowCompare, + /** * Supports 32-bit index buffers. */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index e8410ce429..91ad5596e4 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -453,6 +453,10 @@ private void loadCapabilitiesCommon() { caps.add(Caps.DepthTexture); } + if (gl2 != null || caps.contains(Caps.OpenGLES30)) { + caps.add(Caps.TextureShadowCompare); + } + if (caps.contains(Caps.OpenGL20) || caps.contains(Caps.OpenGLES30) || caps.contains(Caps.WebGL) || hasExtension("GL_OES_depth24")) { caps.add(Caps.Depth24); @@ -2674,7 +2678,7 @@ && isMipmapGenerationSupported(image.getFormat(), } ShadowCompareMode texCompareMode = tex.getShadowCompareMode(); - if ( (gl2 != null || caps.contains(Caps.OpenGLES30)) && curState.shadowCompareMode != texCompareMode) { + if (caps.contains(Caps.TextureShadowCompare) && curState.shadowCompareMode != texCompareMode) { bindTextureAndUnit(target, image, unit); if (texCompareMode != ShadowCompareMode.Off) { gl.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_REF_TO_TEXTURE); diff --git a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java index 62d4c6c5de..7f0a81d94a 100644 --- a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java @@ -377,16 +377,23 @@ public void init() { file = new File(url.getFile()); fileLastM = file.lastModified(); - } catch (NoSuchFieldException - | SecurityException + } catch (NoSuchFieldException ex) { + Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.FINE, + "Material hot reload disabled for {0}; asset URL is not reflectively available.", + fileName); + } catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.FINE, + "Material hot reload disabled for " + fileName, ex); } } } public boolean shouldFire() { + if (file == null || fileLastM == null) { + return false; + } if (file.lastModified() != fileLastM) { fileLastM = file.lastModified(); return true; diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java index 2447800759..0e1e2af20a 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java @@ -105,7 +105,6 @@ public void simpleInitApp() { updateMaterial(); - } @@ -153,4 +152,5 @@ private void updateMaterial() { "Tank material -> metallic: " + metallic + ", roughness: " + roughness + " (N/P, F toggles SH fast path)"); } + } diff --git a/jme3-ios-examples/build.gradle b/jme3-ios-examples/build.gradle new file mode 100644 index 0000000000..b2e6554182 --- /dev/null +++ b/jme3-ios-examples/build.gradle @@ -0,0 +1,385 @@ +buildscript { + repositories { + exclusiveContent { + forRepositories( + maven { + name = 'CentralSnapshots' + url = uri('https://central.sonatype.com/repository/maven-snapshots/') + mavenContent { + snapshotsOnly() + } + } + ) + filter { + includeModule('org.ngengine', 'libjglios-gradle-plugin') + } + } + gradlePluginPortal() + mavenCentral() + } + dependencies { + classpath 'org.ngengine:libjglios-gradle-plugin:0.1.0-SNAPSHOT' + } +} + +import groovy.json.JsonOutput +import java.io.DataInputStream +import java.util.zip.ZipFile + +apply plugin: 'org.ngengine.libjglios' + +description = 'iOS libJGLIOS launcher for jme3-examples.' + +repositories { + exclusiveContent { + forRepositories( + maven { + name = 'CentralSnapshots' + url = uri('https://central.sonatype.com/repository/maven-snapshots/') + mavenContent { + snapshotsOnly() + } + } + ) + filter { + includeModule('org.ngengine', 'libjglios-core-ios') + includeModule('org.ngengine', 'libjglios-gles-ios') + includeModule('org.ngengine', 'libjglios-sdl3-ios') + includeModule('org.ngengine', 'libjglios-openal-ios') + includeModule('org.ngengine', 'libjglios-angle-ios') + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +def examplesJar = project(':jme3-examples').tasks.named('jar') +def generateExamplesTestChooserClassList = project(':jme3-examples').tasks.named('generateTestChooserClassList') +def generatedExamplesTestChooserResourcesDir = project(':jme3-examples').layout.buildDirectory.dir('generated/testchooser/resources') +def examplesTestClassListFile = generatedExamplesTestChooserResourcesDir.map { it.file('jme3test/test-classes.txt') } +def iosChooserLauncherClass = 'jme3test.ios.IosTestChooserLauncher' +def requestedExampleClass = findProperty('example')?.toString()?.trim() +def exampleClass = requestedExampleClass ?: iosChooserLauncherClass +def iosExampleClassesDir = layout.buildDirectory.dir('ios-example-classes') +def iosInitialExampleSourceDir = layout.buildDirectory.dir('generated/ios-initial-example/sources') +def iosNativeImageMetadataDir = layout.buildDirectory.dir('generated/ios-native-image-metadata/resources') +def iosChooserExcludedPrefixes = [ + 'jme3test.awt.', + 'jme3test.bullet.', + 'jme3test.niftygui.', + 'jme3test.opencl.', + 'jme3test.terrain.' +] +def iosChooserExcludedNames = [ + 'jme3test.app.TestChangeAppIcon', + 'jme3test.app.TestContextRestart', + 'jme3test.app.TestMonitorApp', + 'jme3test.app.TestResizableApp', + 'jme3test.asset.TestOnlineJar', + 'jme3test.audio.TestAudioDeviceDisconnect' +] +def isIosChooserClass = { String className -> + !iosChooserExcludedPrefixes.any { className.startsWith(it) } + && !iosChooserExcludedNames.contains(className) + && !className.contains('Jogl') + && !className.contains('Lwjgl') +} +def readExamplesTestClassList = { + def file = examplesTestClassListFile.get().asFile + if (!file.exists()) { + return [] + } + file.readLines('UTF-8') + .collect { it.trim() } + .findAll { it } + .findAll { isIosChooserClass(it) } +} +def selectedIosExampleClasses = { + requestedExampleClass ? [requestedExampleClass] : readExamplesTestClassList() +} +def javaStringLiteral = { String value -> + if (value == null) { + return 'null' + } + '"' + value + .replace('\\', '\\\\') + .replace('"', '\\"') + .replace('\n', '\\n') + .replace('\r', '\\r') + '"' +} +def readClassReferences = { ZipFile zip, String classPath -> + def entry = zip.getEntry("${classPath}.class") + if (entry == null) { + return [] as Set + } + zip.getInputStream(entry).withCloseable { input -> + def data = new DataInputStream(input) + if (data.readInt() != (int) 0xCAFEBABE) { + return [] as Set + } + data.readUnsignedShort() + data.readUnsignedShort() + def constantPool = new Object[data.readUnsignedShort()] + for (int index = 1; index < constantPool.length; index++) { + int tag = data.readUnsignedByte() + switch (tag) { + case 1: + constantPool[index] = data.readUTF() + break + case 3: + case 4: + data.skipBytes(4) + break + case 5: + case 6: + data.skipBytes(8) + index++ + break + case 7: + case 8: + case 16: + case 19: + case 20: + constantPool[index] = [tag: tag, nameIndex: data.readUnsignedShort()] + break + case 9: + case 10: + case 11: + case 12: + case 18: + data.skipBytes(4) + break + case 15: + data.skipBytes(3) + break + case 17: + data.skipBytes(4) + break + default: + throw new GradleException("Unsupported constant-pool tag ${tag} in ${classPath}.class") + } + } + constantPool.findAll { it instanceof Map && it.tag == 7 } + .collect { constantPool[it.nameIndex] } + .findAll { it instanceof String && it.startsWith('jme3test/') && !it.startsWith('jme3test/ios/') } + .collect { it.replace('/', '.') } + .findAll { !it.contains('[') } as Set + } +} +def expandExamplesClassNames = { Collection rootClasses -> + def expanded = new LinkedHashSet(rootClasses) + def jarFile = examplesJar.flatMap { it.archiveFile }.get().asFile + new ZipFile(jarFile).withCloseable { zip -> + def availablePaths = new LinkedHashSet() + zip.entries().each { entry -> + if (entry.directory || !entry.name.endsWith('.class')) { + return + } + def classPath = entry.name.substring(0, entry.name.length() - '.class'.length()) + if (classPath.startsWith('jme3test/')) { + availablePaths.add(classPath) + } + } + + def pending = new ArrayDeque(expanded) + while (!pending.isEmpty()) { + def className = pending.removeFirst() + def rootPath = className.replace('.', '/') + availablePaths.findAll { it.startsWith("${rootPath}\$") }.each { classPath -> + def nestedClass = classPath.replace('/', '.') + if (expanded.add(nestedClass)) { + pending.add(nestedClass) + } + } + readClassReferences(zip, rootPath).each { referencedClass -> + def referencedPath = referencedClass.replace('.', '/') + if (availablePaths.contains(referencedPath) && expanded.add(referencedClass)) { + pending.add(referencedClass) + } + } + } + } + expanded as List +} +def collectRuntimeClassNamesByPrefix = { Collection prefixes -> + def classes = new LinkedHashSet() + sourceSets.main.compileClasspath.files.findAll { it.exists() }.each { file -> + if (file.isDirectory()) { + file.eachFileRecurse { classFile -> + if (!classFile.name.endsWith('.class')) { + return + } + def relativePath = file.toPath().relativize(classFile.toPath()).toString().replace(File.separatorChar, (char) '/') + def className = relativePath.substring(0, relativePath.length() - '.class'.length()).replace('/', '.') + if (prefixes.any { className.startsWith(it) } && !className.endsWith('module-info') && !className.endsWith('package-info')) { + classes.add(className) + } + } + } else if (file.name.endsWith('.jar')) { + new ZipFile(file).withCloseable { zip -> + zip.entries().each { entry -> + if (entry.directory || !entry.name.endsWith('.class')) { + return + } + def className = entry.name.substring(0, entry.name.length() - '.class'.length()).replace('/', '.') + if (prefixes.any { className.startsWith(it) } && !className.endsWith('module-info') && !className.endsWith('package-info')) { + classes.add(className) + } + } + } + } + } + classes as List +} + +def prepareIosExampleClasses = tasks.register('prepareIosExampleClasses', Sync) { + dependsOn examplesJar, generateExamplesTestChooserClassList + inputs.property('exampleClass', exampleClass) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + inputs.property('iosExampleClassExpansionVersion', 'bytecode-reference-v1') + inputs.property('iosExampleIncludes', findProperty('iosExampleIncludes')?.toString() ?: '') + inputs.file(examplesTestClassListFile) + inputs.file(examplesJar.flatMap { it.archiveFile }) + from(zipTree(examplesJar.flatMap { it.archiveFile })) { + def expandedClasses + include { details -> + expandedClasses = expandedClasses ?: expandExamplesClassNames(selectedIosExampleClasses()) + expandedClasses.any { className -> details.path == "${className.replace('.', '/')}.class" } + } + def extraIncludes = findProperty('iosExampleIncludes')?.toString() + if (extraIncludes) { + extraIncludes.split(',').collect { it.trim() }.findAll { it }.each { include it } + } + } + into iosExampleClassesDir +} + +def generateIosInitialExampleSource = tasks.register('generateIosInitialExampleSource') { + def outputFile = iosInitialExampleSourceDir.map { it.file('jme3test/ios/IosInitialExample.java') } + outputs.file(outputFile) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + doLast { + def file = outputFile.get().asFile + file.parentFile.mkdirs() + file.text = """package jme3test.ios; + +final class IosInitialExample { + private static final String CLASS_NAME = ${javaStringLiteral(requestedExampleClass)}; + + private IosInitialExample() { + } + + static String className() { + return CLASS_NAME; + } +} +""" + } +} + +def generateIosNativeImageMetadata = tasks.register('generateIosNativeImageMetadata') { + dependsOn examplesJar, generateExamplesTestChooserClassList, tasks.named('prepareGraalHostNik') + def reflectConfig = iosNativeImageMetadataDir.map { + it.file('META-INF/native-image/org.jmonkeyengine/jme3-ios-testchooser/reflect-config.json') + } + outputs.file(reflectConfig) + inputs.property('exampleClass', exampleClass) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + inputs.property('iosExampleClassExpansionVersion', 'bytecode-reference-v1') + inputs.file(examplesTestClassListFile) + inputs.file(examplesJar.flatMap { it.archiveFile }) + inputs.files({ sourceSets.main.compileClasspath }) + doLast { + def exampleClasses = selectedIosExampleClasses() + def classes = (expandExamplesClassNames(exampleClasses) + + collectRuntimeClassNamesByPrefix(['com.bulletphysics.']) + + [iosChooserLauncherClass, 'jme3test.ios.IosTestChooser', 'jme3test.ios.IosInitialExample']).unique() + def metadata = classes.collect { className -> + [ + name: className, + allDeclaredConstructors: true, + allPublicConstructors: true, + allPublicMethods: true + ] + } + def outputFile = reflectConfig.get().asFile + outputFile.parentFile.mkdirs() + outputFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(metadata)) + System.lineSeparator() + } +} + +libJGLIOS { + mainClass = iosChooserLauncherClass + bundleId = 'org.jmonkeyengine.jme3iosexamples' + appName = 'JmeIosExamples' + simulatorDevice = (findProperty('iosSimulatorDevice') ?: 'iPhone 16').toString() +} + +sourceSets { + main { + java.srcDir iosInitialExampleSourceDir + output.dir(iosExampleClassesDir, builtBy: prepareIosExampleClasses) + resources { + srcDir iosNativeImageMetadataDir + srcDir generatedExamplesTestChooserResourcesDir + srcDir '../jme3-testdata/src/main/resources' + srcDir '../jme3-examples/src/main/resources' + } + } +} + +dependencies { + implementation project(':jme3-core') + implementation project(':jme3-ios') + implementation 'org.ngengine:libjglios-core-ios:0.1.0-SNAPSHOT' + implementation 'org.ngengine:libjglios-gles-ios:0.1.0-SNAPSHOT' + implementation 'org.ngengine:libjglios-sdl3-ios:0.1.0-SNAPSHOT' + implementation 'org.ngengine:libjglios-openal-ios:0.1.0-SNAPSHOT' + implementation 'org.ngengine:libjglios-angle-ios:0.1.0-SNAPSHOT' + implementation project(':jme3-effects') + implementation project(':jme3-jbullet') + implementation project(':jme3-jogg') + implementation project(':jme3-networking') + implementation project(':jme3-plugins') + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') + + if ((findProperty('iosIncludeAwtUnsafeModules') ?: 'false').toString().toBoolean()) { + implementation project(':jme3-niftygui') + implementation project(':jme3-terrain') + } +} + +tasks.named('compileJava') { + dependsOn prepareIosExampleClasses, generateIosInitialExampleSource + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + options.release = 11 +} + +tasks.named('processResources') { + dependsOn generateIosNativeImageMetadata, generateExamplesTestChooserClassList +} + +tasks.register('runIosExamples') { + group = 'verification' + description = 'Builds, installs, and launches the jme3-examples iOS app.' + dependsOn tasks.named('runIosApp') +} + +tasks.register('printIosExamplesRunHelp') { + group = 'help' + description = 'Prints how to run jme3-examples on iOS through libJGLIOS.' + doLast { + println "Run :jme3-ios-examples:runIosExamples for the iOS test chooser." + println "Run :jme3-ios-examples:runIosExamples -Pexample=jme3test.helloworld.HelloJME3 to open one example automatically through the chooser launcher." + println "runIosExamples uses an available connected device automatically, otherwise it opens the simulator." + println "Use -PiosAppTarget=simulator to force simulator, or -PiosDevice='' to force a device." + println "For device signing, add -PiosSigningIdentity, -PiosProvisioningProfile, and optionally -PiosCodesignEntitlements." + println "For examples with helper classes, add -PiosExampleIncludes='jme3test/path/**'." + println "Terrain and niftygui are excluded by default because they pull java.awt into native-image; opt in with -PiosIncludeAwtUnsafeModules=true." + } +} diff --git a/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java b/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java new file mode 100644 index 0000000000..2e6acd5ad5 --- /dev/null +++ b/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java @@ -0,0 +1,428 @@ +package jme3test.ios; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListenerAdapter; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.system.JmeSystem; +import java.util.ArrayList; +import java.util.List; + +public final class IosTestChooser extends SimpleApplication implements ActionListener { + private static final String SELECT_MAPPING = "IosTestChooserSelect"; + private static final float MIN_TEXT_SIZE = 10f; + private static final long TAP_DEBOUNCE_NANOS = 180_000_000L; + private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0.045f, 0.060f, 0.050f, 1f); + private static final ColorRGBA EXAMPLE_BUTTON_COLOR = new ColorRGBA(0.135f, 0.305f, 0.145f, 1f); + private static final ColorRGBA ACTION_BUTTON_COLOR = new ColorRGBA(0.760f, 0.360f, 0.060f, 1f); + private static final ColorRGBA DISABLED_BUTTON_COLOR = new ColorRGBA(0.090f, 0.105f, 0.095f, 1f); + private static final ColorRGBA SEARCH_BUTTON_COLOR = new ColorRGBA(0.070f, 0.085f, 0.075f, 1f); + private static final ColorRGBA PRIMARY_TEXT_COLOR = new ColorRGBA(0.930f, 0.960f, 0.900f, 1f); + private static final ColorRGBA MUTED_TEXT_COLOR = new ColorRGBA(0.500f, 0.560f, 0.480f, 1f); + private static final ColorRGBA SEARCH_TEXT_COLOR = new ColorRGBA(0.765f, 0.910f, 0.555f, 1f); + + private final List