diff options
Diffstat (limited to 'src')
42 files changed, 2714 insertions, 191 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/GLDrawable.java b/src/jogl/classes/com/jogamp/opengl/GLDrawable.java index c801ba463..a4a9d2e06 100644 --- a/src/jogl/classes/com/jogamp/opengl/GLDrawable.java +++ b/src/jogl/classes/com/jogamp/opengl/GLDrawable.java @@ -134,7 +134,7 @@ public interface GLDrawable extends NativeSurfaceHolder { public void setRealized(boolean realized); /** - * Returns <code>true</code> if this drawable is realized, otherwise <code>true</code>. + * Returns <code>true</code> if this drawable is realized, otherwise <code>false</code>. * <p> * A drawable can be realized and unrealized via {@link #setRealized(boolean)}. * </p> diff --git a/src/jogl/classes/com/jogamp/opengl/JoglVersion.java b/src/jogl/classes/com/jogamp/opengl/JoglVersion.java index 7589b3776..560d99025 100644 --- a/src/jogl/classes/com/jogamp/opengl/JoglVersion.java +++ b/src/jogl/classes/com/jogamp/opengl/JoglVersion.java @@ -144,8 +144,7 @@ public class JoglVersion extends JogampVersion { } sb.append(VersionUtil.SEPERATOR).append(Platform.getNewline()); - sb.append(device.getClass().getSimpleName()).append("[type ") - .append(device.getType()).append(", connection ").append(device.getConnection()).append("]: ").append(Platform.getNewline()); + sb.append(device.toString()).append(':').append(Platform.getNewline()); if( withAvailabilityInfo ) { GLProfile.glAvailabilityToString(device, sb, "\t", 1); } diff --git a/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java index 5f59f7e6d..6af7bcda4 100644 --- a/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java +++ b/src/jogl/classes/com/jogamp/opengl/awt/GLCanvas.java @@ -157,12 +157,12 @@ import jogamp.opengl.awt.AWTTilePainter; * <li><pre>sun.awt.noerasebackground=true</pre></li> * </ul> * - * <p> - * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a> + * <h5><a name="contextSharing">OpenGL Context Sharing</a></h5> + * * To share a {@link GLContext} see the following note in the documentation overview: * <a href="../../../../overview-summary.html#SHARING">context sharing</a> * as well as {@link GLSharedContextSetter}. - * </p> + * */ @SuppressWarnings("serial") diff --git a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java index 173c394ba..532db3a7a 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java +++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java @@ -148,9 +148,9 @@ public class ShaderCode { switch (type) { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: - case GL3.GL_GEOMETRY_SHADER: - case GL3.GL_TESS_CONTROL_SHADER: - case GL3.GL_TESS_EVALUATION_SHADER: + case GL3ES3.GL_GEOMETRY_SHADER: + case GL3ES3.GL_TESS_CONTROL_SHADER: + case GL3ES3.GL_TESS_EVALUATION_SHADER: case GL3ES3.GL_COMPUTE_SHADER: break; default: @@ -179,9 +179,9 @@ public class ShaderCode { switch (type) { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: - case GL3.GL_GEOMETRY_SHADER: - case GL3.GL_TESS_CONTROL_SHADER: - case GL3.GL_TESS_EVALUATION_SHADER: + case GL3ES3.GL_GEOMETRY_SHADER: + case GL3ES3.GL_TESS_CONTROL_SHADER: + case GL3ES3.GL_TESS_EVALUATION_SHADER: case GL3ES3.GL_COMPUTE_SHADER: break; default: @@ -386,11 +386,11 @@ public class ShaderCode { return binary?SUFFIX_VERTEX_BINARY:SUFFIX_VERTEX_SOURCE; case GL2ES2.GL_FRAGMENT_SHADER: return binary?SUFFIX_FRAGMENT_BINARY:SUFFIX_FRAGMENT_SOURCE; - case GL3.GL_GEOMETRY_SHADER: + case GL3ES3.GL_GEOMETRY_SHADER: return binary?SUFFIX_GEOMETRY_BINARY:SUFFIX_GEOMETRY_SOURCE; - case GL3.GL_TESS_CONTROL_SHADER: + case GL3ES3.GL_TESS_CONTROL_SHADER: return binary?SUFFIX_TESS_CONTROL_BINARY:SUFFIX_TESS_CONTROL_SOURCE; - case GL3.GL_TESS_EVALUATION_SHADER: + case GL3ES3.GL_TESS_EVALUATION_SHADER: return binary?SUFFIX_TESS_EVALUATION_BINARY:SUFFIX_TESS_EVALUATION_SOURCE; case GL3ES3.GL_COMPUTE_SHADER: return binary?SUFFIX_COMPUTE_BINARY:SUFFIX_COMPUTE_SOURCE; @@ -785,11 +785,11 @@ public class ShaderCode { return "VERTEX_SHADER"; case GL2ES2.GL_FRAGMENT_SHADER: return "FRAGMENT_SHADER"; - case GL3.GL_GEOMETRY_SHADER: + case GL3ES3.GL_GEOMETRY_SHADER: return "GEOMETRY_SHADER"; - case GL3.GL_TESS_CONTROL_SHADER: + case GL3ES3.GL_TESS_CONTROL_SHADER: return "TESS_CONTROL_SHADER"; - case GL3.GL_TESS_EVALUATION_SHADER: + case GL3ES3.GL_TESS_EVALUATION_SHADER: return "TESS_EVALUATION_SHADER"; case GL3ES3.GL_COMPUTE_SHADER: return "COMPUTE_SHADER"; @@ -1098,7 +1098,15 @@ public class ShaderCode { while ((line = reader.readLine()) != null) { lineno++; if (line.startsWith("#include ")) { - final String includeFile = line.substring(9).trim(); + final String includeFile; + { + String s = line.substring(9).trim(); + // Bug 1283: Remove shader include filename quotes if exists at start and end only + if( s.startsWith("\"") && s.endsWith("\"")) { + s = s.substring(1, s.length()-1); + } + includeFile = s; + } URLConnection nextConn = null; // Try relative of current shader location @@ -1330,9 +1338,9 @@ public class ShaderCode { // GLSL [ 1.30 .. 1.50 [ needs at least fragement float default precision! switch ( shaderType ) { case GL2ES2.GL_VERTEX_SHADER: - case GL3.GL_GEOMETRY_SHADER: - case GL3.GL_TESS_CONTROL_SHADER: - case GL3.GL_TESS_EVALUATION_SHADER: + case GL3ES3.GL_GEOMETRY_SHADER: + case GL3ES3.GL_TESS_CONTROL_SHADER: + case GL3ES3.GL_TESS_EVALUATION_SHADER: defaultPrecision = gl3_default_precision_vp_gp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = gl3_default_precision_fp; break; diff --git a/src/jogl/classes/jogamp/opengl/GLBufferStateTracker.java b/src/jogl/classes/jogamp/opengl/GLBufferStateTracker.java index 2aa4c4297..a5bd85eb1 100644 --- a/src/jogl/classes/jogamp/opengl/GLBufferStateTracker.java +++ b/src/jogl/classes/jogamp/opengl/GLBufferStateTracker.java @@ -40,9 +40,13 @@ package jogamp.opengl; -import com.jogamp.opengl.*; import com.jogamp.common.util.IntIntHashMap; import com.jogamp.common.util.PropertyAccess; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES3; +import com.jogamp.opengl.GL3ES3; +import com.jogamp.opengl.GL4; +import com.jogamp.opengl.GLException; /** * Tracks as closely as possible which OpenGL buffer object is bound @@ -119,6 +123,8 @@ public class GLBufferStateTracker { * GL_ELEMENT_ARRAY_BUFFER, * GL_PIXEL_PACK_BUFFER, * GL_PIXEL_UNPACK_BUFFER, + * GL_QUERY_BUFFER, + * GL_PARAMETER_BUFFER_ARB, * GL_SHADER_STORAGE_BUFFER, * GL_TEXTURE_BUFFER, * GL_TRANSFORM_FEEDBACK_BUFFER or @@ -139,8 +145,9 @@ public class GLBufferStateTracker { case GL2ES3.GL_PIXEL_PACK_BUFFER: return GL2ES3.GL_PIXEL_PACK_BUFFER_BINDING; case GL2ES3.GL_PIXEL_UNPACK_BUFFER: return GL2ES3.GL_PIXEL_UNPACK_BUFFER_BINDING; case GL4.GL_QUERY_BUFFER: return GL4.GL_QUERY_BUFFER_BINDING; + case GL4.GL_PARAMETER_BUFFER_ARB: return GL4.GL_PARAMETER_BUFFER_BINDING_ARB; // ARB_indirect_parameters case GL3ES3.GL_SHADER_STORAGE_BUFFER: return GL3ES3.GL_SHADER_STORAGE_BUFFER_BINDING; - case GL2GL3.GL_TEXTURE_BUFFER: return GL2GL3.GL_TEXTURE_BINDING_BUFFER; + case GL2ES3.GL_TEXTURE_BUFFER: return GL2ES3.GL_TEXTURE_BINDING_BUFFER; case GL2ES3.GL_TRANSFORM_FEEDBACK_BUFFER: return GL2ES3.GL_TRANSFORM_FEEDBACK_BUFFER_BINDING; case GL2ES3.GL_UNIFORM_BUFFER: return GL2ES3.GL_UNIFORM_BUFFER_BINDING; @@ -162,8 +169,9 @@ public class GLBufferStateTracker { case GL2ES3.GL_PIXEL_PACK_BUFFER: case GL2ES3.GL_PIXEL_UNPACK_BUFFER: case GL4.GL_QUERY_BUFFER: + case GL4.GL_PARAMETER_BUFFER_ARB: // ARB_indirect_parameters case GL3ES3.GL_SHADER_STORAGE_BUFFER: - case GL2GL3.GL_TEXTURE_BUFFER: + case GL2ES3.GL_TEXTURE_BUFFER: case GL2ES3.GL_TRANSFORM_FEEDBACK_BUFFER: case GL2ES3.GL_UNIFORM_BUFFER: diff --git a/src/jogl/classes/jogamp/opengl/GLContextImpl.java b/src/jogl/classes/jogamp/opengl/GLContextImpl.java index 31a8e489e..6866374bc 100644 --- a/src/jogl/classes/jogamp/opengl/GLContextImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLContextImpl.java @@ -2133,7 +2133,7 @@ public abstract class GLContextImpl extends GLContext { final boolean isES = 0 != ( ctp & GLContext.CTX_PROFILE_ES ); final boolean isX11 = NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true); final boolean isWindows = Platform.getOSType() == Platform.OSType.WINDOWS; - final boolean isDriverMesa = glRenderer.contains(MesaSP) || glRenderer.contains("Gallium "); + final boolean isDriverMesa = glRenderer.contains(MesaSP) || glRenderer.contains("Gallium ") || glVersion.contains(MesaSP); final boolean isDriverATICatalyst; final boolean isDriverNVIDIAGeForce; @@ -2347,8 +2347,10 @@ public abstract class GLContextImpl extends GLContext { if( isDriverMesa ) { final VersionNumber mesaSafeFBOVersion = new VersionNumber(8, 0, 0); final VersionNumber mesaIntelBuggySharedCtx921 = new VersionNumber(9, 2, 1); + final VersionNumber mesaSafeDoubleBufferedPBuffer = new VersionNumber(18, 2, 2); // Mesa 18.2.2 + final VersionNumber mesaSafeSetSwapIntervalPostRetarget = mesaSafeDoubleBufferedPBuffer; // Mesa 18.2.2 - { + if( vendorVersion.compareTo(mesaSafeSetSwapIntervalPostRetarget) < 0 ) { final int quirk = GLRendererQuirks.NoSetSwapIntervalPostRetarget; if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); @@ -2357,11 +2359,13 @@ public abstract class GLContextImpl extends GLContext { } if( hwAccel ) { // hardware-acceleration - final int quirk = GLRendererQuirks.NoDoubleBufferedPBuffer; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); + if( vendorVersion.compareTo(mesaSafeDoubleBufferedPBuffer) < 0 ) { + final int quirk = GLRendererQuirks.NoDoubleBufferedPBuffer; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); + } + quirks.addQuirk( quirk ); } - quirks.addQuirk( quirk ); } else { // software if( vendorVersion.compareTo(mesaSafeFBOVersion) < 0 ) { // FIXME: Is it fixed in >= 8.0.0 ? diff --git a/src/jogl/classes/jogamp/opengl/egl/EGLES2DynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/egl/EGLES2DynamicLibraryBundleInfo.java index d37efc455..038194d58 100644 --- a/src/jogl/classes/jogamp/opengl/egl/EGLES2DynamicLibraryBundleInfo.java +++ b/src/jogl/classes/jogamp/opengl/egl/EGLES2DynamicLibraryBundleInfo.java @@ -31,6 +31,8 @@ package jogamp.opengl.egl; import java.util.ArrayList; import java.util.List; +import jogamp.nativewindow.BcmVCArtifacts; + /** * <p> * Covering ES3 and ES2. @@ -41,12 +43,20 @@ public final class EGLES2DynamicLibraryBundleInfo extends EGLDynamicLibraryBundl super(); } + @Override public final List<List<String>> getToolLibNames() { + final List<List<String>> libsList = new ArrayList<List<String>>(); { final List<String> libsGL = new ArrayList<String>(); + /** + * Prefer libGLESv2.so over libGLESv2.so.2 for proprietary + * Broadcom graphics when the VC4 DRM Xorg driver isn't present + */ + final boolean bcm_vc_iv_quirk = BcmVCArtifacts.guessVCIVUsed(); + // ES3: This is the default lib name, according to the spec libsGL.add("libGLESv3.so.3"); @@ -63,12 +73,18 @@ public final class EGLES2DynamicLibraryBundleInfo extends EGLDynamicLibraryBundl libsGL.add("libGLES30"); // ES2: This is the default lib name, according to the spec - libsGL.add("libGLESv2.so.2"); + if (!bcm_vc_iv_quirk) { + libsGL.add("libGLESv2.so.2"); + } // ES2: Try these as well, if spec fails libsGL.add("libGLESv2.so"); libsGL.add("GLESv2"); + if (bcm_vc_iv_quirk) { + libsGL.add("libGLESv2.so.2"); + } + // ES2: Alternative names libsGL.add("GLES20"); libsGL.add("GLESv2_CM"); diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp index 22dd1e61a..516aa0f6f 100644 --- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp +++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColor.fp @@ -6,10 +6,10 @@ #define mgl_FragColor gl_FragColor #endif -#include es_precision.glsl +#include "es_precision.glsl" -#include mgl_uniform.glsl -#include mgl_varying.glsl +#include "mgl_uniform.glsl" +#include "mgl_varying.glsl" #include mgl_alphatest.fp diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp index 130711e19..8a610f062 100644 --- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp +++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/shaders/FixedFuncColorTexture.fp @@ -8,14 +8,14 @@ #endif -#include es_precision.glsl -#include mgl_lightdef.glsl +#include "es_precision.glsl" +#include "mgl_lightdef.glsl" -#include mgl_const.glsl -#include mgl_uniform.glsl -#include mgl_varying.glsl +#include "mgl_const.glsl" +#include "mgl_uniform.glsl" +#include "mgl_varying.glsl" -#include mgl_alphatest.fp +#include "mgl_alphatest.fp" const float gamma = 1.5; // FIXME const vec3 igammav = vec3(1.0 / gamma); // FIXME diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowFactory.java b/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowFactory.java index 5da7974b0..323bc8c86 100644 --- a/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowFactory.java +++ b/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowFactory.java @@ -33,7 +33,6 @@ package com.jogamp.nativewindow; -import java.io.File; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; @@ -43,9 +42,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.jogamp.nativewindow.util.Point; import com.jogamp.nativewindow.util.PointImmutable; import jogamp.common.os.PlatformPropsImpl; +import jogamp.nativewindow.BcmVCArtifacts; import jogamp.nativewindow.Debug; import jogamp.nativewindow.NativeWindowFactoryImpl; import jogamp.nativewindow.ToolkitProperties; @@ -82,28 +83,28 @@ public abstract class NativeWindowFactory { protected static final boolean DEBUG; /** OpenKODE/EGL type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}.*/ - public static final String TYPE_EGL = ".egl".intern(); + public static final String TYPE_EGL = ".egl"; /** Microsoft Windows type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_WINDOWS = ".windows".intern(); + public static final String TYPE_WINDOWS = ".windows"; /** X11 type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_X11 = ".x11".intern(); + public static final String TYPE_X11 = ".x11"; /** Broadcom VC IV/EGL type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_BCM_VC_IV = ".bcm.vc.iv".intern(); + public static final String TYPE_BCM_VC_IV = ".bcm.vc.iv"; /** Android/EGL type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}.*/ - public static final String TYPE_ANDROID = ".android".intern(); + public static final String TYPE_ANDROID = ".android"; /** Mac OS X type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_MACOSX = ".macosx".intern(); + public static final String TYPE_MACOSX = ".macosx"; /** Generic AWT type, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_AWT = ".awt".intern(); + public static final String TYPE_AWT = ".awt"; /** Generic DEFAULT type, where platform implementation don't care, as retrieved with {@link #getNativeWindowType(boolean)}. String is canonical via {@link String#intern()}. */ - public static final String TYPE_DEFAULT = ".default".intern(); + public static final String TYPE_DEFAULT = ".default"; private static final String nativeWindowingTypePure; // canonical String via String.intern() private static final String nativeWindowingTypeCustom; // canonical String via String.intern() @@ -136,20 +137,6 @@ public abstract class NativeWindowFactory { protected NativeWindowFactory() { } - private static final boolean guessBroadcomVCIV() { - return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { - private final File vcliblocation = new File( - "/opt/vc/lib/libbcm_host.so"); - @Override - public Boolean run() { - if ( vcliblocation.isFile() ) { - return Boolean.TRUE; - } - return Boolean.FALSE; - } - } ).booleanValue(); - } - private static String _getNativeWindowingType() { switch(PlatformPropsImpl.OS_TYPE) { case ANDROID: @@ -166,7 +153,7 @@ public abstract class NativeWindowFactory { case SUNOS: case HPUX: default: - if( guessBroadcomVCIV() ) { + if( BcmVCArtifacts.guessVCIVUsed() ) { return TYPE_BCM_VC_IV; } return TYPE_X11; @@ -747,7 +734,7 @@ public abstract class NativeWindowFactory { * FIXME: Bug 973 Needs service provider interface (SPI) for TK dependent implementation * </p> */ - public static PointImmutable getLocationOnScreen(final NativeWindow nw) { + public static Point getLocationOnScreen(final NativeWindow nw) { final String nwt = NativeWindowFactory.getNativeWindowType(true); if( NativeWindowFactory.TYPE_X11 == nwt ) { return X11Lib.GetRelativeLocation(nw.getDisplayHandle(), nw.getScreenIndex(), nw.getWindowHandle(), 0, 0, 0); diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowHolder.java b/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowHolder.java new file mode 100644 index 000000000..8cf9bcb1a --- /dev/null +++ b/src/nativewindow/classes/com/jogamp/nativewindow/NativeWindowHolder.java @@ -0,0 +1,41 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.nativewindow; + +/** + * Accessor interface for implementing classes with ownership of a {@link NativeWindow} + * via an <i>is-a</i> or <i>has-a</i> relation. + */ +public interface NativeWindowHolder extends NativeSurfaceHolder { + /** + * Returns the associated {@link NativeWindow} of this {@link NativeWindowHolder}, which is identical to {@link #getNativeSurface()} + */ + public NativeWindow getNativeWindow(); +} + diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/javafx/JFXAccessor.java b/src/nativewindow/classes/com/jogamp/nativewindow/javafx/JFXAccessor.java new file mode 100644 index 000000000..bffabdd5a --- /dev/null +++ b/src/nativewindow/classes/com/jogamp/nativewindow/javafx/JFXAccessor.java @@ -0,0 +1,291 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.nativewindow.javafx; + +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import com.jogamp.nativewindow.AbstractGraphicsDevice; +import com.jogamp.nativewindow.AbstractGraphicsScreen; +import com.jogamp.nativewindow.NativeWindowException; +import com.jogamp.nativewindow.NativeWindowFactory; +import com.jogamp.nativewindow.VisualIDHolder; +import com.jogamp.nativewindow.macosx.MacOSXGraphicsDevice; +import com.jogamp.nativewindow.windows.WindowsGraphicsDevice; +import com.jogamp.nativewindow.x11.X11GraphicsDevice; +import com.jogamp.common.ExceptionUtils; +import com.jogamp.common.util.InterruptedRuntimeException; +import com.jogamp.common.util.ReflectionUtil; +import com.jogamp.common.util.RunnableTask; +import com.sun.javafx.tk.TKStage; + +import javafx.application.Platform; +import javafx.stage.Window; +import jogamp.nativewindow.Debug; +import jogamp.nativewindow.x11.X11Lib; +import jogamp.nativewindow.x11.X11Util; + +public class JFXAccessor { + private static final boolean DEBUG; + + private static final boolean jfxAvailable; + private static final Method fxUserThreadGetter; + private static final Method tkStageGetter; + private static final Method glassWindowGetter; + private static final Method nativeWindowGetter; + + private static final String nwt; + private static final boolean isOSX; + private static final boolean isWindows; + private static final boolean isX11; + + static { + final boolean[] _DEBUG = new boolean[] { true }; + + final Method[] res = AccessController.doPrivileged(new PrivilegedAction<Method[]>() { + @Override + public Method[] run() { + NativeWindowFactory.initSingleton(); // last resort .. + final Method[] res = new Method[] { null, null, null, null }; + try { + int i=0; + _DEBUG[0] = Debug.debug("JFX"); + /** + * com.sun.javafx.tk.Toolkit + */ + final Class<?> jfxToolkitClz = ReflectionUtil.getClass("com.sun.javafx.tk.Toolkit", false, JFXAccessor.class.getClassLoader()); + res[i] = jfxToolkitClz.getDeclaredMethod("getFxUserThread"); + res[i++].setAccessible(true); + + /*** + * class javafx.stage.Window + * class javafx.stage.Stage extends javafx.stage.Window + * class com.sun.javafx.tk.quantum.WindowStage extends com.sun.javafx.tk.quantum.GlassStage implements com.sun.javafx.tk.TKStage + * abstract com.sun.glass.ui.Window + * + * javafx.stage.Window: com.sun.javafx.tk.TKStage [impl_]getPeer() + * com.sun.javafx.tk.quantum.WindowStage: final com.sun.glass.ui.Window getPlatformWindow() + * com.sun.glass.ui.Window: public long getNativeWindow() + */ + final Class<?> jfxStageWindowClz = ReflectionUtil.getClass("javafx.stage.Window", false, JFXAccessor.class.getClassLoader()); + // final Class<?> jfxTkTKStageClz = ReflectionUtil.getClass("com.sun.javafx.tk.TKStage", false, JFXAccessor.class.getClassLoader()); + final Class<?> jfxTkQuWindowStageClz = ReflectionUtil.getClass("com.sun.javafx.tk.quantum.WindowStage", false, JFXAccessor.class.getClassLoader()); + final Class<?> jfxGlassUiWindowClz = ReflectionUtil.getClass("com.sun.glass.ui.Window", false, JFXAccessor.class.getClassLoader()); + + try { + // jfx 9, 11, 12, .. + res[i] = jfxStageWindowClz.getDeclaredMethod("getPeer"); + } catch (final NoSuchMethodException ex) { + // jfx 8 + res[i] = jfxStageWindowClz.getDeclaredMethod("impl_getPeer"); + } + res[i++].setAccessible(true); + + res[i] = jfxTkQuWindowStageClz.getDeclaredMethod("getPlatformWindow"); + res[i++].setAccessible(true); + res[i] = jfxGlassUiWindowClz.getDeclaredMethod("getNativeWindow"); + res[i++].setAccessible(true); + } catch (final Throwable t) { + if(_DEBUG[0]) { + ExceptionUtils.dumpThrowable("jfx-init", t); + } + } + return res; + } + }); + { + int i=0; + fxUserThreadGetter = res[i++]; + tkStageGetter = res[i++]; + glassWindowGetter = res[i++]; + nativeWindowGetter = res[i++]; + } + jfxAvailable = null != fxUserThreadGetter && null != tkStageGetter && null != glassWindowGetter && null != nativeWindowGetter; + + nwt = NativeWindowFactory.getNativeWindowType(false); + isOSX = NativeWindowFactory.TYPE_MACOSX == nwt; + isWindows = NativeWindowFactory.TYPE_WINDOWS == nwt; + isX11 = NativeWindowFactory.TYPE_X11 == nwt; + + DEBUG = _DEBUG[0]; + if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - Info: JFXAccessor.<init> available "+jfxAvailable+", nwt "+nwt+"( x11 "+isX11+", win "+isWindows+", osx "+isOSX+")"); + } + } + + // + // Common any toolkit + // + + public static boolean isJFXAvailable() { return jfxAvailable; } + + /** + * Runs given {@code task} on the JFX Thread if it has not stopped and if caller is not already on the JFX Thread, + * otherwise execute given {@code task} on the current thread. + * @param wait + * @param task + * @see #isJFXThreadOrHasJFXThreadStopped() + */ + public static void runOnJFXThread(final boolean wait, final Runnable task) { + final Object rTaskLock = new Object(); + synchronized(rTaskLock) { // lock the task execution + if( isJFXThreadOrHasJFXThreadStopped() ) { + task.run(); + } else if( !wait ) { + Platform.runLater(task); + } else { + final RunnableTask rTask = new RunnableTask(task, + rTaskLock, + true /* always catch and report Exceptions, don't disturb EDT */, + null); + Platform.runLater(rTask); + try { + while( rTask.isInQueue() ) { + rTaskLock.wait(); // free lock, allow execution of rTask + } + } catch (final InterruptedException ie) { + throw new InterruptedRuntimeException(ie); + } + final Throwable throwable = rTask.getThrowable(); + if(null!=throwable) { + if(throwable instanceof NativeWindowException) { + throw (NativeWindowException)throwable; + } + throw new RuntimeException(throwable); + } + } + } + } + + public static Thread getJFXThread() throws NativeWindowException { + try { + return (Thread) fxUserThreadGetter.invoke(null); + } catch (final Throwable e) { + throw new NativeWindowException("Error getting JFX-Thread", e); + } + } + public static String getJFXThreadName() { + final Thread t = getJFXThread(); + return null != t ? t.getName() : null; + } + /** + * @return true if the JFX Thread has stopped + */ + public static boolean hasJFXThreadStopped() { + final Thread t = getJFXThread(); + return null == t || !t.isAlive(); + } + /** + * @return true if caller is on the JFX Thread + */ + public static boolean isJFXThread() { + final Thread t = getJFXThread(); + return Thread.currentThread() == t; + } + /** + * @return true if the JFX Thread has stopped or if caller is on the JFX Thread + */ + public static boolean isJFXThreadOrHasJFXThreadStopped() { + final Thread t = getJFXThread(); + return null == t || !t.isAlive() || Thread.currentThread() == t; + } + + /** + * @param stageWindow the JavaFX top heavyweight window handle + * @return the AbstractGraphicsDevice w/ the native device handle + * @throws NativeWindowException if an exception occurs retrieving the window handle or deriving the native device + * @throws UnsupportedOperationException if the windowing system is not supported + */ + public static AbstractGraphicsDevice getDevice(final Window stageWindow) throws NativeWindowException, UnsupportedOperationException { + if( isX11 ) { + // Decoupled X11 Device/Screen allowing X11 display lock-free off-thread rendering + final String connection = null; + final long x11DeviceHandle = X11Util.openDisplay(connection); + if( 0 == x11DeviceHandle ) { + throw new NativeWindowException("Error creating display: "+connection); + } + return new X11GraphicsDevice(x11DeviceHandle, AbstractGraphicsDevice.DEFAULT_UNIT, true /* owner */); + } + if( isWindows ) { + return new WindowsGraphicsDevice(AbstractGraphicsDevice.DEFAULT_UNIT); + } + if( isOSX ) { + return new MacOSXGraphicsDevice(AbstractGraphicsDevice.DEFAULT_UNIT); + } + throw new UnsupportedOperationException("n/a for this windowing system: "+nwt); + } + + /** + * @param device + * @param screen -1 is default screen of the given device, e.g. maybe 0 or determined by native API. >= 0 is specific screen + * @return + */ + public static AbstractGraphicsScreen getScreen(final AbstractGraphicsDevice device, final int screen) { + return NativeWindowFactory.createScreen(device, screen); + } + + public static int getNativeVisualID(final AbstractGraphicsDevice device, final long windowHandle) { + if( isX11 ) { + return X11Lib.GetVisualIDFromWindow(device.getHandle(), windowHandle); + } + if( isWindows || isOSX ) { + return VisualIDHolder.VID_UNDEFINED; + } + throw new UnsupportedOperationException("n/a for this windowing system: "+nwt); + } + + /** + * @param stageWindow the JavaFX top heavyweight window handle + * @return the native window handle + * @throws NativeWindowException if an exception occurs retrieving the window handle + */ + public static long getWindowHandle(final Window stageWindow) throws NativeWindowException { + final long h[] = { 0 }; + runOnJFXThread(true, new Runnable() { + public void run() { + try { + final TKStage tkStage = (TKStage) tkStageGetter.invoke(stageWindow); + if( null != tkStage ) { + final Object platformWindow = glassWindowGetter.invoke(tkStage); + if( null != platformWindow ) { + final Object nativeHandle = nativeWindowGetter.invoke(platformWindow); + h[0] = ((Long) nativeHandle).longValue(); + } else if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - Info: JFXAccessor null GlassWindow"); + } + } else if(DEBUG) { + System.err.println(Thread.currentThread().getName()+" - Info: JFXAccessor null TKStage"); + } + } catch (final Throwable e) { + throw new NativeWindowException("Error getting Window handle", e); + } + } }); + return h[0]; + } +} diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/swt/SWTAccessor.java b/src/nativewindow/classes/com/jogamp/nativewindow/swt/SWTAccessor.java index c79ed2f67..6ddfc8950 100644 --- a/src/nativewindow/classes/com/jogamp/nativewindow/swt/SWTAccessor.java +++ b/src/nativewindow/classes/com/jogamp/nativewindow/swt/SWTAccessor.java @@ -51,9 +51,10 @@ import com.jogamp.nativewindow.x11.X11GraphicsDevice; import jogamp.nativewindow.macosx.OSXUtil; import jogamp.nativewindow.x11.X11Lib; +import jogamp.nativewindow.Debug; public class SWTAccessor { - private static final boolean DEBUG = true; + private static final boolean DEBUG = Debug.debug("SWT"); private static final Field swt_control_handle; private static final boolean swt_uses_long_handles; diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/util/Point.java b/src/nativewindow/classes/com/jogamp/nativewindow/util/Point.java index fc5465bbf..aa511b625 100644 --- a/src/nativewindow/classes/com/jogamp/nativewindow/util/Point.java +++ b/src/nativewindow/classes/com/jogamp/nativewindow/util/Point.java @@ -120,6 +120,18 @@ public class Point implements Cloneable, PointImmutable { /** * Translate this instance's x- and y-components, + * i.e. add the values of the given delta point to them. + * @param pd delta point + * @return this instance for scaling + */ + public final Point translate(final PointImmutable pd) { + x += pd.getX() ; + y += pd.getY() ; + return this; + } + + /** + * Translate this instance's x- and y-components, * i.e. add the given deltas to them. * @param dx delta for x * @param dy delta for y diff --git a/src/nativewindow/classes/jogamp/nativewindow/BcmVCArtifacts.java b/src/nativewindow/classes/jogamp/nativewindow/BcmVCArtifacts.java new file mode 100644 index 000000000..216c55a3a --- /dev/null +++ b/src/nativewindow/classes/jogamp/nativewindow/BcmVCArtifacts.java @@ -0,0 +1,76 @@ +/** + * Copyright 2018 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package jogamp.nativewindow; + +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Heuristics about Broadcom (BCM) VideoCore (VC) existence and usage + */ +public class BcmVCArtifacts { + static final boolean hasVCLib; + static final boolean hasVC4ModLocation; + static final boolean hasDriCard0File; + + static { + final File vcLibLocation = new File( + "/opt/vc/lib/libbcm_host.so"); + final File vc4ModLocation = new File( + "/sys/module/vc4"); + final File driCard0Location = new File( + "/dev/dri/card0"); + final boolean[] res = new boolean [3]; + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + res[0] = vcLibLocation.isFile(); + res[1] = vc4ModLocation.isDirectory(); + res[2] = driCard0Location.isFile(); + return null; + } } ); + hasVCLib = res[0]; + hasVC4ModLocation = res[1]; + hasDriCard0File = res[2]; + } + + /** + * @return True if proprietary BCM VC IV is probably being present + */ + public static final boolean guessVCIVPresent() { + return hasVCLib; + } + /** + * @return True if proprietary BCM VC IV is probably being used and not Xorg drivers + */ + public static final boolean guessVCIVUsed() { + return hasVCLib && !hasVC4ModLocation && !hasDriCard0File; + } +} diff --git a/src/nativewindow/native/macosx/OSXmisc.m b/src/nativewindow/native/macosx/OSXmisc.m index ce4a3b7ee..c04ba786e 100644 --- a/src/nativewindow/native/macosx/OSXmisc.m +++ b/src/nativewindow/native/macosx/OSXmisc.m @@ -336,6 +336,7 @@ JNIEXPORT jlong JNICALL Java_jogamp_nativewindow_macosx_OSXUtil_CreateNSWindow0 (JNIEnv *env, jclass unused, jint x, jint y, jint width, jint height) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [CATransaction begin]; NSRect rect = NSMakeRect(x, y, width, height); // Allocate the window @@ -365,6 +366,7 @@ NS_ENDHANDLER // [myView lockFocus]; // [myView unlockFocus]; + [CATransaction commit]; [pool release]; return (jlong) ((intptr_t) myWindow); @@ -379,9 +381,17 @@ JNIEXPORT void JNICALL Java_jogamp_nativewindow_macosx_OSXUtil_DestroyNSWindow0 (JNIEnv *env, jclass unused, jlong nsWindow) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - NSWindow* mWin = (NSWindow*) ((intptr_t) nsWindow); + [CATransaction begin]; +NS_DURING + NSWindow* mWin = (NSWindow*) ((intptr_t) nsWindow); [mWin close]; // performs release! +NS_HANDLER + // On killing or terminating the process [NSWindow _close], rarely + // throws an NSRangeException while ordering out menu items +NS_ENDHANDLER + + [CATransaction commit]; [pool release]; } diff --git a/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java b/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java index ae32fd164..a0083b4ea 100644 --- a/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java +++ b/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java @@ -49,7 +49,9 @@ import java.security.PrivilegedAction; import java.util.Set; import com.jogamp.nativewindow.CapabilitiesImmutable; +import com.jogamp.nativewindow.NativeSurface; import com.jogamp.nativewindow.NativeWindow; +import com.jogamp.nativewindow.NativeWindowHolder; import com.jogamp.nativewindow.OffscreenLayerOption; import com.jogamp.nativewindow.WindowClosingProtocol; import com.jogamp.opengl.GLAnimatorControl; @@ -100,7 +102,7 @@ import com.jogamp.opengl.util.TileRenderer; * the underlying JAWT mechanism to composite the image, if supported. */ @SuppressWarnings("serial") -public class NewtCanvasAWT extends java.awt.Canvas implements WindowClosingProtocol, OffscreenLayerOption, AWTPrintLifecycle { +public class NewtCanvasAWT extends java.awt.Canvas implements NativeWindowHolder, WindowClosingProtocol, OffscreenLayerOption, AWTPrintLifecycle { public static final boolean DEBUG = Debug.debug("Window"); private final Object sync = new Object(); @@ -110,7 +112,7 @@ public class NewtCanvasAWT extends java.awt.Canvas implements WindowClosingProto private Window newtChild = null; private boolean newtChildAttached = false; private boolean isOnscreen = true; - private WindowClosingMode newtChildCloseOp; + private WindowClosingMode newtChildCloseOp = WindowClosingMode.DISPOSE_ON_CLOSE; private final AWTParentWindowAdapter awtWinAdapter; private final AWTAdapter awtMouseAdapter; private final AWTAdapter awtKeyAdapter; @@ -420,10 +422,22 @@ public class NewtCanvasAWT extends java.awt.Canvas implements WindowClosingProto return newtChild; } - /** @return this AWT Canvas NativeWindow representation, may be null in case {@link #removeNotify()} has been called, - * or {@link #addNotify()} hasn't been called yet.*/ + /** + * {@inheritDoc} + * @return this AWT Canvas {@link NativeWindow} representation, may be null in case {@link #removeNotify()} has been called, + * or {@link #addNotify()} hasn't been called yet. + */ + @Override public NativeWindow getNativeWindow() { return jawtWindow; } + /** + * {@inheritDoc} + * @return this AWT Canvas {@link NativeSurface} representation, may be null in case {@link #removeNotify()} has been called, + * or {@link #addNotify()} hasn't been called yet. + */ + @Override + public NativeSurface getNativeSurface() { return jawtWindow; } + @Override public WindowClosingMode getDefaultCloseOperation() { return awtWindowClosingProtocol.getDefaultCloseOperation(); diff --git a/src/newt/classes/com/jogamp/newt/javafx/NewtCanvasJFX.java b/src/newt/classes/com/jogamp/newt/javafx/NewtCanvasJFX.java new file mode 100644 index 000000000..e04ed326d --- /dev/null +++ b/src/newt/classes/com/jogamp/newt/javafx/NewtCanvasJFX.java @@ -0,0 +1,670 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.newt.javafx; + +import com.jogamp.common.util.PropertyAccess; +import com.jogamp.nativewindow.AbstractGraphicsConfiguration; +import com.jogamp.nativewindow.AbstractGraphicsScreen; +import com.jogamp.nativewindow.Capabilities; +import com.jogamp.nativewindow.CapabilitiesImmutable; +import com.jogamp.nativewindow.GraphicsConfigurationFactory; +import com.jogamp.nativewindow.NativeSurface; +import com.jogamp.nativewindow.NativeWindow; +import com.jogamp.nativewindow.NativeWindowException; +import com.jogamp.nativewindow.NativeWindowFactory; +import com.jogamp.nativewindow.NativeWindowHolder; +import com.jogamp.nativewindow.SurfaceUpdatedListener; +import com.jogamp.nativewindow.WindowClosingProtocol; +import com.jogamp.nativewindow.WindowClosingProtocol.WindowClosingMode; +import com.jogamp.nativewindow.util.Insets; +import com.jogamp.nativewindow.util.InsetsImmutable; +import com.jogamp.nativewindow.util.Point; +import com.jogamp.nativewindow.util.Rectangle; +import com.jogamp.opengl.GLCapabilities; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.Bounds; +import javafx.scene.Scene; +import javafx.scene.canvas.Canvas; +import jogamp.newt.Debug; +import jogamp.newt.javafx.JFXEDTUtil; + +import com.jogamp.nativewindow.javafx.JFXAccessor; +import com.jogamp.newt.Display; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.util.EDTUtil; + +/** + * A NEWT based JFX {@link Canvas} specialization allowing a NEWT child {@link Window} to be attached using native parenting. + * <p> + * {@link NewtCanvasJFX} allows utilizing custom {@link GLCapabilities} settings independent from the JavaFX's window + * as well as independent rendering from JavaFX's thread. + * </p> + * <p> + * {@link NewtCanvasJFX} allows native parenting operations before and after + * it's belonging Group's Scene has been attached to the JavaFX {@link javafx.stage.Window Window}'s actual native window, + * i.e. becoming fully realized and visible. + * </p> + * <p> + * Note that {@link JFXAccessor#runOnJFXThread(boolean, Runnable)} is still used to for certain + * mandatory JavaFX lifecycle operation on the JavaFX thread. + * </p> + */ +public class NewtCanvasJFX extends Canvas implements NativeWindowHolder, WindowClosingProtocol { + private static final boolean DEBUG = Debug.debug("Window"); + private static final boolean USE_JFX_EDT = PropertyAccess.getBooleanProperty("jogamp.newt.javafx.UseJFXEDT", true, true); + private volatile javafx.stage.Window parentWindow = null; + private volatile AbstractGraphicsScreen screen = null; + + private WindowClosingMode newtChildClosingMode = WindowClosingMode.DISPOSE_ON_CLOSE; + private WindowClosingMode closingMode = WindowClosingMode.DISPOSE_ON_CLOSE; + private final Rectangle clientArea = new Rectangle(); + + private volatile JFXNativeWindow nativeWindow = null; + private volatile Window newtChild = null; + private volatile boolean newtChildReady = false; // ready if JFXEDTUtil is set and newtChild parented + private volatile boolean postSetSize = false; // pending resize + private volatile boolean postSetPos = false; // pending pos + + private final EventHandler<javafx.stage.WindowEvent> windowClosingListener = new EventHandler<javafx.stage.WindowEvent>() { + public final void handle(final javafx.stage.WindowEvent e) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.DISPOSE, "+e+", closeOp "+closingMode); + } + if( WindowClosingMode.DISPOSE_ON_CLOSE == closingMode ) { + NewtCanvasJFX.this.destroy(); + } else { + // avoid JavaFX closing operation + e.consume(); + } + } }; + private final EventHandler<javafx.stage.WindowEvent> windowShownListener = new EventHandler<javafx.stage.WindowEvent>() { + public final void handle(final javafx.stage.WindowEvent e) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.SHOWN, "+e); + } + repaintAction(true); + } }; + + /** + * Instantiates a NewtCanvas with a NEWT child. + * + * <p> + * Note: The NEWT child {@link Display}'s {@link EDTUtil} is being set to an JFX conform implementation + * via {@link Display#setEDTUtil(EDTUtil)}. + * </p> + * @param child optional preassigned {@link #Window}, maybe null + */ + public NewtCanvasJFX(final Window child) { + super(); + + updateParentWindowAndScreen(); + + final ChangeListener<Number> sizeListener = new ChangeListener<Number>() { + @Override public void changed(final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.Size, "+oldValue.doubleValue()+" -> "+newValue.doubleValue()+", has "+getWidth()+"x"+getHeight()); + } + updateSizeCheck((int)getWidth(), (int)getHeight()); + repaintAction(isVisible()); + } }; + this.widthProperty().addListener(sizeListener); + this.heightProperty().addListener(sizeListener); + this.visibleProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue, final Boolean newValue) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.Visible, "+oldValue.booleanValue()+" -> "+newValue.booleanValue()+", has "+isVisible()); + } + repaintAction(newValue.booleanValue()); + } + }); + this.sceneProperty().addListener(new ChangeListener<Scene>() { + @Override public void changed(final ObservableValue<? extends Scene> observable, final Scene oldValue, final Scene newValue) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.Scene, "+oldValue+" -> "+newValue+", has "+getScene()); + if(null != newValue) { + final javafx.stage.Window w = newValue.getWindow(); + System.err.println("NewtCanvasJFX.Event.Scene window "+w+" (showing "+(null!=w?w.isShowing():0)+")"); + } + } + if( updateParentWindowAndScreen() ) { + repaintAction(isVisible()); + } + } + }); + + if(null != child) { + setNEWTChild(child); + } + } + + private final void repaintAction(final boolean visible) { + if( visible && ( null != nativeWindow || validateNative(true /* completeReparent */) ) ) { + if( newtChildReady ) { + if( postSetSize ) { + newtChild.setSize(clientArea.getWidth(), clientArea.getHeight()); + postSetSize = false; + } + if( postSetPos ) { + newtChild.setPosition(clientArea.getX(), clientArea.getY()); + postSetPos = false; + } + newtChild.windowRepaint(0, 0, clientArea.getWidth(), clientArea.getHeight()); + } + } + } + + private final void updatePosSizeCheck() { + final Bounds b = localToScene(getBoundsInLocal()); + updatePosCheck((int)b.getMinX(), (int)b.getMinY()); + updateSizeCheck((int)getWidth(), (int)getHeight()); + } + private final void updatePosCheck(final int newX, final int newY) { + final boolean posChanged; + { + final Rectangle oClientArea = clientArea; + posChanged = newX != oClientArea.getX() || newY != oClientArea.getY(); + if( posChanged ) { + clientArea.setX(newX); + clientArea.setY(newY); + } + } + if(DEBUG) { + final long nsh = newtChildReady ? newtChild.getSurfaceHandle() : 0; + System.err.println("NewtCanvasJFX.updatePosCheck: posChanged "+posChanged+", ("+Thread.currentThread().getName()+"): newtChildReady "+newtChildReady+", "+clientArea.getX()+"/"+clientArea.getY()+" "+clientArea.getWidth()+"x"+clientArea.getHeight()+" - surfaceHandle 0x"+Long.toHexString(nsh)); + } + if( posChanged ) { + if( newtChildReady ) { + newtChild.setPosition(clientArea.getX(), clientArea.getY()); + } else { + postSetPos = true; + } + } + } + private final void updateSizeCheck(final int newWidth, final int newHeight) { + final boolean sizeChanged; + { + final Rectangle oClientArea = clientArea; + sizeChanged = newWidth != oClientArea.getWidth() || newHeight != oClientArea.getHeight(); + if( sizeChanged ) { + clientArea.setWidth(newWidth); + clientArea.setHeight(newHeight); + } + } + if(DEBUG) { + final long nsh = newtChildReady ? newtChild.getSurfaceHandle() : 0; + System.err.println("NewtCanvasJFX.updateSizeCheck: sizeChanged "+sizeChanged+", ("+Thread.currentThread().getName()+"): newtChildReady "+newtChildReady+", "+clientArea.getX()+"/"+clientArea.getY()+" "+clientArea.getWidth()+"x"+clientArea.getHeight()+" - surfaceHandle 0x"+Long.toHexString(nsh)); + } + if( sizeChanged ) { + if( newtChildReady ) { + newtChild.setSize(clientArea.getWidth(), clientArea.getHeight()); + } else { + postSetSize = true; + } + } + } + + private final ChangeListener<javafx.stage.Window> sceneWindowChangeListener = new ChangeListener<javafx.stage.Window>() { + @Override public void changed(final ObservableValue<? extends javafx.stage.Window> observable, final javafx.stage.Window oldValue, final javafx.stage.Window newValue) { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.Event.Window, "+oldValue+" -> "+newValue); + } + if( updateParentWindowAndScreen() ) { + repaintAction(isVisible()); + } + } }; + + private boolean updateParentWindowAndScreen() { + final Scene s = this.getScene(); + if( null != s ) { + final javafx.stage.Window w = s.getWindow(); + if( DEBUG ) { + System.err.println("NewtCanvasJFX.updateParentWindowAndScreen: Scene "+s+", Window "+w+" (showing "+(null!=w?w.isShowing():0)+")"); + } + if( w != parentWindow ) { + destroyImpl(false); + } + parentWindow = w; + if( null != w ) { + screen = JFXAccessor.getScreen(JFXAccessor.getDevice(parentWindow), -1 /* default */); + parentWindow.addEventHandler(javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST, windowClosingListener); + parentWindow.addEventHandler(javafx.stage.WindowEvent.WINDOW_SHOWN, windowShownListener); + return true; + } else { + s.windowProperty().addListener(sceneWindowChangeListener); + } + } else { + if( DEBUG ) { + System.err.println("NewtCanvasJFX.updateParentWindowAndScreen: Null Scene"); + } + if( null != parentWindow ) { + destroyImpl(false); + } + } + return false; + } + + /** + * Destroys this resource: + * <ul> + * <li> Make the NEWT Child invisible </li> + * <li> Disconnects the NEWT Child from this Canvas NativeWindow, reparent to NULL </li> + * <li> Issues {@link Window#destroy()} on the NEWT Child</li> + * <li> Remove reference to the NEWT Child</li> + * </ul> + * JavaFX will issue this call when sending out the {@link javafx.stage.WindowEvent#WINDOW_CLOSE_REQUEST} automatically, + * if the user has not overridden the default {@link WindowClosingMode#DISPOSE_ON_CLOSE} to {@link WindowClosingMode#DO_NOTHING_ON_CLOSE} + * via {@link #setDefaultCloseOperation(com.jogamp.nativewindow.WindowClosingProtocol.WindowClosingMode)}. + * @see Window#destroy() + * @see #setDefaultCloseOperation(com.jogamp.nativewindow.WindowClosingProtocol.WindowClosingMode) + */ + public void destroy() { + destroyImpl(true); + } + private void destroyImpl(final boolean disposeNewtChild) { + if(DEBUG) { + System.err.println("NewtCanvasJFX.dispose: (has parent "+(null!=parentWindow)+", hasNative "+(null!=nativeWindow)+",\n\t"+newtChild); + } + if( null != newtChild ) { + if(DEBUG) { + System.err.println("NewtCanvasJFX.dispose.1: EDTUtil cur "+newtChild.getScreen().getDisplay().getEDTUtil()); + } + if( null != nativeWindow ) { + configureNewtChild(false); + newtChild.setVisible(false); + newtChild.reparentWindow(null, -1, -1, 0 /* hint */); + } + if( disposeNewtChild ) { + newtChild.destroy(); + newtChild = null; + } + } + if( null != parentWindow ) { + parentWindow.getScene().windowProperty().removeListener(sceneWindowChangeListener); + parentWindow.removeEventHandler(javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST, windowClosingListener); + parentWindow.removeEventHandler(javafx.stage.WindowEvent.WINDOW_SHOWN, windowShownListener); + parentWindow = null; + } + if( null != screen ) { + screen.getDevice().close(); + screen = null; + } + nativeWindow = null; + } + + private final boolean validateNative(final boolean completeReparent) { + if( null == parentWindow ) { + return false; + } + assert null == nativeWindow; + updatePosSizeCheck(); + if(0 >= clientArea.getWidth() || 0 >= clientArea.getHeight()) { + return false; + } + final long nativeWindowHandle = JFXAccessor.getWindowHandle(parentWindow); + if( 0 == nativeWindowHandle ) { + return false; + } + screen.getDevice().open(); + + /* Native handle for the control, used to associate with GLContext */ + final int visualID = JFXAccessor.getNativeVisualID(screen.getDevice(), nativeWindowHandle); + final boolean visualIDValid = NativeWindowFactory.isNativeVisualIDValidForProcessing(visualID); + if(DEBUG) { + System.err.println("NewtCanvasJFX.validateNative() windowHandle 0x"+Long.toHexString(nativeWindowHandle)+", visualID 0x"+Integer.toHexString(visualID)+", valid "+visualIDValid); + } + if( visualIDValid ) { + /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite). + * Note: JFX is owner of the native handle, hence no closing operation will be a NOP. */ + final CapabilitiesImmutable caps = new Capabilities(); + final GraphicsConfigurationFactory factory = GraphicsConfigurationFactory.getFactory(screen.getDevice(), caps); + final AbstractGraphicsConfiguration config = factory.chooseGraphicsConfiguration( caps, caps, null, screen, visualID ); + if(DEBUG) { + System.err.println("NewtCanvasJFX.validateNative() factory: "+factory+", windowHandle 0x"+Long.toHexString(nativeWindowHandle)+", visualID 0x"+Integer.toHexString(visualID)+", chosen config: "+config); + // Thread.dumpStack(); + } + if (null == config) { + throw new NativeWindowException("Error choosing GraphicsConfiguration creating window: "+this); + } + + nativeWindow = new JFXNativeWindow(config, nativeWindowHandle); + if( completeReparent ) { + reparentWindow( true ); + } + } + + return null != nativeWindow; + } + + /** + * Sets a new NEWT child, provoking reparenting. + * <p> + * A previously detached <code>newChild</code> will be released to top-level status + * and made invisible. + * </p> + * <p> + * Note: When switching NEWT child's, detaching the previous first via <code>setNEWTChild(null)</code> + * produced much cleaner visual results. + * </p> + * <p> + * Note: The NEWT child {@link Display}'s {@link EDTUtil} is being set to an JFX conform implementation + * via {@link Display#setEDTUtil(EDTUtil)}. + * </p> + * @return the previous attached newt child. + */ + public Window setNEWTChild(final Window newChild) { + final Window prevChild = newtChild; + if(DEBUG) { + System.err.println("NewtCanvasJFX.setNEWTChild.0: win "+newtWinHandleToHexString(prevChild)+" -> "+newtWinHandleToHexString(newChild)); + } + // remove old one + if(null != newtChild) { + reparentWindow( false ); + newtChild = null; + } + // add new one, reparent only if ready + newtChild = newChild; + if( null != newtChild && ( null != nativeWindow || validateNative(false /* completeReparent */) ) ) { + reparentWindow( true ); + } + return prevChild; + } + + private void reparentWindow(final boolean add) { + if( null == newtChild ) { + return; // nop + } + if(DEBUG) { + System.err.println("NewtCanvasJFX.reparentWindow.0: add="+add+", win "+newtWinHandleToHexString(newtChild)+", EDTUtil: cur "+newtChild.getScreen().getDisplay().getEDTUtil()); + } + + newtChild.setFocusAction(null); // no AWT focus traversal .. + if(add) { + assert null != nativeWindow && null != parentWindow; + updatePosSizeCheck(); + final int x = clientArea.getX(); + final int y = clientArea.getY(); + final int w = clientArea.getWidth(); + final int h = clientArea.getHeight(); + + if(USE_JFX_EDT) { + // setup JFX EDT and start it + final Display newtDisplay = newtChild.getScreen().getDisplay(); + final EDTUtil oldEDTUtil = newtDisplay.getEDTUtil(); + if( ! ( oldEDTUtil instanceof JFXEDTUtil ) ) { + final EDTUtil newEDTUtil = new JFXEDTUtil(newtDisplay); + if(DEBUG) { + System.err.println("NewtCanvasJFX.reparentWindow.1: replacing EDTUtil "+oldEDTUtil+" -> "+newEDTUtil); + } + newEDTUtil.start(); + newtDisplay.setEDTUtil( newEDTUtil ); + } + } + + newtChild.setSize(w, h); + newtChild.reparentWindow(nativeWindow, x, y, Window.REPARENT_HINT_BECOMES_VISIBLE); + newtChild.setPosition(x, y); + newtChild.setVisible(true); + configureNewtChild(true); + newtChild.sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout to listener + + // force this JFX Canvas to be focus-able, + // since it is completely covered by the newtChild (z-order). + // FIXME ??? super.requestFocus(); + } else { + configureNewtChild(false); + newtChild.setVisible(false); + newtChild.reparentWindow(null, -1, -1, 0 /* hints */); + } + if(DEBUG) { + System.err.println("NewtCanvasJFX.reparentWindow.X: add="+add+", win "+newtWinHandleToHexString(newtChild)+", EDTUtil: cur "+newtChild.getScreen().getDisplay().getEDTUtil()); + } + } + + private void configureNewtChild(final boolean attach) { + newtChildReady = attach; + if( null != newtChild ) { + newtChild.setKeyboardFocusHandler(null); + if(attach) { + newtChildClosingMode = newtChild.setDefaultCloseOperation(WindowClosingMode.DO_NOTHING_ON_CLOSE); + } else { + newtChild.setFocusAction(null); + newtChild.setDefaultCloseOperation(newtChildClosingMode); + } + } + } + + /** @return the current NEWT child */ + public Window getNEWTChild() { + return newtChild; + } + + /** + * {@inheritDoc} + * @return this JFX Canvas {@link NativeWindow} representation, may be null in case it has not been realized + */ + @Override + public NativeWindow getNativeWindow() { return nativeWindow; } + + /** + * {@inheritDoc} + * @return this JFX Canvas {@link NativeSurface} representation, may be null in case it has not been realized + */ + @Override + public NativeSurface getNativeSurface() { return nativeWindow; } + + @Override + public WindowClosingMode getDefaultCloseOperation() { + return closingMode; + } + + @Override + public WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) { + final WindowClosingMode old = closingMode; + closingMode = op; + return old; + } + + + boolean isParent() { + return null!=newtChild ; + } + + boolean isFullscreen() { + return null != newtChild && newtChild.isFullscreen(); + } + + private final void requestFocusNEWTChild() { + if( newtChildReady ) { + newtChild.setFocusAction(null); + newtChild.requestFocus(); + } + } + + @Override + public void requestFocus() { + NewtCanvasJFX.super.requestFocus(); + requestFocusNEWTChild(); + } + + private class JFXNativeWindow implements NativeWindow { + private final AbstractGraphicsConfiguration config; + private final long nativeWindowHandle; + private final InsetsImmutable insets; // only required to allow proper client position calculation on OSX + + public JFXNativeWindow(final AbstractGraphicsConfiguration config, final long nativeWindowHandle) { + this.config = config; + this.nativeWindowHandle = nativeWindowHandle; + this.insets = new Insets(0, 0, 0, 0); + } + + @Override + public int lockSurface() throws NativeWindowException, RuntimeException { + return NativeSurface.LOCK_SUCCESS; + } + + @Override + public void unlockSurface() { } + + @Override + public boolean isSurfaceLockedByOtherThread() { + return false; + } + + @Override + public Thread getSurfaceLockOwner() { + return null; + } + + @Override + public boolean surfaceSwap() { + return false; + } + + @Override + public void addSurfaceUpdatedListener(final SurfaceUpdatedListener l) { } + + @Override + public void addSurfaceUpdatedListener(final int index, final SurfaceUpdatedListener l) throws IndexOutOfBoundsException { + } + + @Override + public void removeSurfaceUpdatedListener(final SurfaceUpdatedListener l) { } + + @Override + public long getSurfaceHandle() { + return 0; + } + + @Override + public int getWidth() { + return getSurfaceWidth(); // FIXME: Use 'scale' or an actual window-width + } + + @Override + public int getHeight() { + return getSurfaceHeight(); // FIXME: Use 'scale' or an actual window-width + } + + @Override + public final int[] convertToWindowUnits(final int[] pixelUnitsAndResult) { + return pixelUnitsAndResult; // FIXME HiDPI: use 'pixelScale' + } + + @Override + public final int[] convertToPixelUnits(final int[] windowUnitsAndResult) { + return windowUnitsAndResult; // FIXME HiDPI: use 'pixelScale' + } + + @Override + public int getSurfaceWidth() { + return clientArea.getWidth(); + } + + @Override + public int getSurfaceHeight() { + return clientArea.getHeight(); + } + + @Override + public final NativeSurface getNativeSurface() { return this; } + + @Override + public AbstractGraphicsConfiguration getGraphicsConfiguration() { + return config; + } + + @Override + public long getDisplayHandle() { + return config.getScreen().getDevice().getHandle(); + } + + @Override + public int getScreenIndex() { + return config.getScreen().getIndex(); + } + + @Override + public void surfaceUpdated(final Object updater, final NativeSurface ns, final long when) { } + + @Override + public void destroy() { } + + @Override + public NativeWindow getParent() { + return null; + } + + @Override + public long getWindowHandle() { + return nativeWindowHandle; + } + + @Override + public InsetsImmutable getInsets() { + return insets; + } + + @Override + public int getX() { + return NewtCanvasJFX.this.clientArea.getX(); + } + + @Override + public int getY() { + return NewtCanvasJFX.this.clientArea.getY(); + } + + @Override + public Point getLocationOnScreen(final Point point) { + final Point los = NativeWindowFactory.getLocationOnScreen(this); // client window location on screen + if(null!=point) { + return point.translate(los); + } else { + return los; + } + } + + @Override + public boolean hasFocus() { + return isFocused(); + } + }; + + static String newtWinHandleToHexString(final Window w) { + return null != w ? toHexString(w.getWindowHandle()) : "nil"; + } + static String toHexString(final long l) { + return "0x"+Long.toHexString(l); + } +} + diff --git a/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java b/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java index 84c4683db..568b5d0bb 100644 --- a/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java +++ b/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java @@ -34,6 +34,7 @@ import java.util.List; import com.jogamp.common.util.IOUtil; import com.jogamp.nativewindow.CapabilitiesImmutable; import com.jogamp.nativewindow.ScalableSurface; +import com.jogamp.newt.Window; import com.jogamp.newt.Display; import com.jogamp.newt.Display.PointerIcon; import com.jogamp.newt.event.KeyEvent; @@ -47,12 +48,37 @@ import com.jogamp.opengl.FPSCounter; import com.jogamp.opengl.GL; import com.jogamp.opengl.GLAnimatorControl; import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLDrawable; import com.jogamp.opengl.GLRunnable; import com.jogamp.opengl.util.Gamma; import com.jogamp.opengl.util.PNGPixelRect; import jogamp.newt.driver.PNGIcon; +/** + * NEWT {@link GLWindow} Demo functionality + * <ul> + * <li>SPACE: Toggle animator {@link GLAnimatorControl#pause() pause}/{@link GLAnimatorControl#resume() resume}</li> + * <li>A: Toggle window {@link Window#setAlwaysOnTop(boolean) always on top}</li> + * <li>B: Toggle window {@link Window#setAlwaysOnBottom(boolean) always on bottom}</li> + * <li>C: Toggle different {@link Window#setPointerIcon(PointerIcon) pointer icons}</li> + * <li>D: Toggle window {@link Window#setUndecorated(boolean) decoration on/off}</li> + * <li>F: Toggle window {@link Window#setFullscreen(boolean) fullscreen on/off}</li> + * <li>Three-Finger Double-Tap: Toggle window {@link Window#setFullscreen(boolean) fullscreen on/off}</li> + * <li>G: Increase {@link Gamma#setDisplayGamma(GLDrawable, float, float, float) gamma} by 0.1, +SHIFT decrease gamma by 0.1</li> + * <li>I: Toggle {@link Window#setPointerVisible(boolean) pointer visbility}</li> + * <li>J: Toggle {@link Window#confinePointer(boolean) pointer jail (confine to window)}</li> + * <li>M: Toggle {@link Window#setMaximized(boolean, boolean) window maximized}: Y, +CTRL off, +SHIFT toggle X+Y, +ALT X</li> + * <li>P: Set window {@link Window#setPosition(int, int) position to 100/100}</li> + * <li>Q: Quit</li> + * <li>R: Toggle window {@link Window#setResizable(boolean) resizable}</li> + * <li>S: Toggle window {@link Window#setSticky(boolean) sticky}</li> + * <li>V: Toggle window {@link Window#setVisible(boolean) visibility} for 5s</li> + * <li>V: +CTRL: Rotate {@link GL#setSwapInterval(int) swap interval} -1, 0, 1</li> + * <li>W: {@link Window#warpPointer(int, int) Warp pointer} to center of window</li> + * <li>X: Toggle {@link ScalableSurface#setSurfaceScale(float[]) [{@link ScalableSurface#IDENTITY_PIXELSCALE}, {@link ScalableSurface#AUTOMAX_PIXELSCALE}]</li> + * </ul> + */ public class NEWTDemoListener extends WindowAdapter implements KeyListener, MouseListener { protected final GLWindow glWindow; final PointerIcon[] pointerIcons; @@ -230,12 +256,6 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous printlnState("[set maximize post]", "max[vert "+vert+", horz "+horz+"]"); } } ); break; - case KeyEvent.VK_Q: - if( quitAdapterEnabled && 0 == e.getModifiers() ) { - System.err.println("QUIT Key "+Thread.currentThread()); - quitAdapterShouldQuit = true; - } - break; case KeyEvent.VK_P: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { @@ -245,6 +265,12 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous printlnState("[set position post]"); } } ); break; + case KeyEvent.VK_Q: + if( quitAdapterEnabled && 0 == e.getModifiers() ) { + System.err.println("QUIT Key "+Thread.currentThread()); + quitAdapterShouldQuit = true; + } + break; case KeyEvent.VK_R: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { diff --git a/src/newt/classes/com/jogamp/newt/swt/NewtCanvasSWT.java b/src/newt/classes/com/jogamp/newt/swt/NewtCanvasSWT.java index 186ffb162..d1bd638d8 100644 --- a/src/newt/classes/com/jogamp/newt/swt/NewtCanvasSWT.java +++ b/src/newt/classes/com/jogamp/newt/swt/NewtCanvasSWT.java @@ -38,8 +38,10 @@ import com.jogamp.nativewindow.NativeSurface; import com.jogamp.nativewindow.NativeWindow; import com.jogamp.nativewindow.NativeWindowException; import com.jogamp.nativewindow.NativeWindowFactory; +import com.jogamp.nativewindow.NativeWindowHolder; import com.jogamp.nativewindow.SurfaceUpdatedListener; import com.jogamp.nativewindow.WindowClosingProtocol; +import com.jogamp.nativewindow.WindowClosingProtocol.WindowClosingMode; import com.jogamp.nativewindow.util.Insets; import com.jogamp.nativewindow.util.InsetsImmutable; import com.jogamp.nativewindow.util.Point; @@ -70,12 +72,13 @@ import com.jogamp.newt.util.EDTUtil; * Implementation allows use of custom {@link GLCapabilities}. * </p> */ -public class NewtCanvasSWT extends Canvas implements WindowClosingProtocol { +public class NewtCanvasSWT extends Canvas implements NativeWindowHolder, WindowClosingProtocol { private static final boolean DEBUG = Debug.debug("Window"); private final AbstractGraphicsScreen screen; - private WindowClosingMode newtChildCloseOp = WindowClosingMode.DISPOSE_ON_CLOSE; + private WindowClosingMode newtChildClosingMode = WindowClosingMode.DISPOSE_ON_CLOSE; + private final WindowClosingMode closingMode = WindowClosingMode.DISPOSE_ON_CLOSE; private volatile Rectangle clientArea; private volatile SWTNativeWindow nativeWindow; @@ -330,17 +333,28 @@ public class NewtCanvasSWT extends Canvas implements WindowClosingProtocol { return new Point(parentLoc[0].x, parentLoc[0].y); } - /** @return this SWT Canvas NativeWindow representation, may be null in case it has not been realized. */ + /** + * {@inheritDoc} + * @return this SWT Canvas {@link NativeWindow} representation, may be null in case it has not been realized + */ + @Override public NativeWindow getNativeWindow() { return nativeWindow; } + /** + * {@inheritDoc} + * @return this SWT Canvas {@link NativeSurface} representation, may be null in case it has not been realized + */ + @Override + public NativeSurface getNativeSurface() { return nativeWindow; } + @Override public WindowClosingMode getDefaultCloseOperation() { - return newtChildCloseOp; // TODO: implement ?! + return closingMode; } @Override public WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) { - return newtChildCloseOp = op; // TODO: implement ?! + return closingMode; // TODO: implement! } @@ -401,10 +415,10 @@ public class NewtCanvasSWT extends Canvas implements WindowClosingProtocol { if( null != newtChild ) { newtChild.setKeyboardFocusHandler(null); if(attach) { - newtChildCloseOp = newtChild.setDefaultCloseOperation(WindowClosingMode.DO_NOTHING_ON_CLOSE); + newtChildClosingMode = newtChild.setDefaultCloseOperation(WindowClosingMode.DO_NOTHING_ON_CLOSE); } else { newtChild.setFocusAction(null); - newtChild.setDefaultCloseOperation(newtChildCloseOp); + newtChild.setDefaultCloseOperation(newtChildClosingMode); } } } diff --git a/src/newt/classes/com/jogamp/newt/util/applet/JOGLNewtAppletBase.java b/src/newt/classes/com/jogamp/newt/util/applet/JOGLNewtAppletBase.java index 5194d9416..c30576ff4 100644 --- a/src/newt/classes/com/jogamp/newt/util/applet/JOGLNewtAppletBase.java +++ b/src/newt/classes/com/jogamp/newt/util/applet/JOGLNewtAppletBase.java @@ -56,9 +56,13 @@ import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; -/** Shows how to deploy an applet using JOGL. This demo must be - referenced from a web page via an <applet> tag. */ - +/** + * Shows how to deploy an applet using JOGL. + * This demo must be referenced from a web page via an <applet> tag. + * <p> + * The demo code uses {@link NEWTDemoListener} functionality. + * </p> + */ public class JOGLNewtAppletBase implements KeyListener, GLEventListener { public static final boolean DEBUG = Debug.debug("Applet"); diff --git a/src/newt/classes/jogamp/newt/driver/bcm/vc/iv/ScreenDriver.java b/src/newt/classes/jogamp/newt/driver/bcm/vc/iv/ScreenDriver.java index f236edd6b..b4af4045c 100644 --- a/src/newt/classes/jogamp/newt/driver/bcm/vc/iv/ScreenDriver.java +++ b/src/newt/classes/jogamp/newt/driver/bcm/vc/iv/ScreenDriver.java @@ -84,11 +84,11 @@ public class ScreenDriver extends ScreenImpl { props[i++] = 0; // rotated viewport x pixel-units props[i++] = 0; // rotated viewport y pixel-units props[i++] = cachedWidth; // rotated viewport width pixel-units - props[i++] = cachedWidth; // rotated viewport height pixel-units + props[i++] = cachedHeight; // rotated viewport height pixel-units props[i++] = 0; // rotated viewport x window-units props[i++] = 0; // rotated viewport y window-units props[i++] = cachedWidth; // rotated viewport width window-units - props[i++] = cachedWidth; // rotated viewport height window-units + props[i++] = cachedHeight; // rotated viewport height window-units MonitorModeProps.streamInMonitorDevice(cache, this, currentMode, null, cache.monitorModes, props, 0, null); } diff --git a/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java index 9e1d2869b..468aca654 100644 --- a/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java +++ b/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java @@ -406,6 +406,20 @@ public class WindowDriver extends WindowImpl { } } + public final void sendTouchScreenEvent(final short eventType, final int modifiers, + final int pActionIdx, final int[] pNames, + final int[] pX, final int[] pY, final float[] pPressure, final float maxPressure) { + final int pCount = pNames.length; + final MouseEvent.PointerType[] pTypes = new MouseEvent.PointerType[pCount]; + for(int i=0; i<pCount; i++) + { + pTypes[i] = MouseEvent.PointerType.TouchScreen; + } + doPointerEvent(false /*enqueue*/, false /*wait*/, + pTypes, eventType, modifiers, pActionIdx, true /*normalPNames*/, pNames, + pX, pY, pPressure, maxPressure, new float[] { 0f, 0f, 0f} /*rotationXYZ*/, 1f/*rotationScale*/); + } + @Override public final void sendKeyEvent(final short eventType, final int modifiers, final short keyCode, final short keySym, final char keyChar) { throw new InternalError("XXX: Adapt Java Code to Native Code Changes"); diff --git a/src/newt/classes/jogamp/newt/driver/x11/X11UnderlayTracker.java b/src/newt/classes/jogamp/newt/driver/x11/X11UnderlayTracker.java index 6bb5b9144..d7e184a3c 100644 --- a/src/newt/classes/jogamp/newt/driver/x11/X11UnderlayTracker.java +++ b/src/newt/classes/jogamp/newt/driver/x11/X11UnderlayTracker.java @@ -323,7 +323,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_CLICKED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -334,7 +334,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_ENTERED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -345,7 +345,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_EXITED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -356,7 +356,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_PRESSED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -367,7 +367,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_RELEASED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -378,7 +378,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_MOVED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -389,7 +389,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } @@ -400,7 +400,7 @@ public class X11UnderlayTracker implements WindowListener, KeyListener, MouseLis if (underlayWindowMap.containsKey(s)) { WindowImpl overlayWindow = underlayWindowMap.get(s); overlayWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_WHEEL_MOVED, 0, - e.getX(), e.getY(), (short) 0, 0); + e.getX(), e.getY(), e.getButton(), 0); } } diff --git a/src/newt/classes/jogamp/newt/javafx/JFXEDTUtil.java b/src/newt/classes/jogamp/newt/javafx/JFXEDTUtil.java new file mode 100644 index 000000000..4ee15b43d --- /dev/null +++ b/src/newt/classes/jogamp/newt/javafx/JFXEDTUtil.java @@ -0,0 +1,358 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.newt.javafx; + +import com.jogamp.nativewindow.NativeWindowException; +import com.jogamp.nativewindow.javafx.JFXAccessor; + +import jogamp.newt.Debug; + +import com.jogamp.common.ExceptionUtils; +import com.jogamp.common.util.InterruptSource; +import com.jogamp.common.util.InterruptedRuntimeException; +import com.jogamp.common.util.RunnableTask; +import com.jogamp.newt.util.EDTUtil; + +import javafx.application.Platform; + +/** + * Simple {@link EDTUtil} implementation utilizing the JFX UI thread + * of the given {@link Display}. + */ +public class JFXEDTUtil implements EDTUtil { + public static final boolean DEBUG = Debug.debug("EDT"); + + private final Object edtLock = new Object(); // locking the EDT start/stop state + private final ThreadGroup threadGroup; + private final String name; + private final Runnable dispatchMessages; + private NEDT nedt = null; + private int start_iter=0; + private static long pollPeriod = EDTUtil.defaultEDTPollPeriod; + + public JFXEDTUtil(final com.jogamp.newt.Display newtDisplay) { + this.threadGroup = Thread.currentThread().getThreadGroup(); + this.name=Thread.currentThread().getName()+"-JFXDisplay-"+newtDisplay.getFQName()+"-EDT-"; + this.dispatchMessages = new Runnable() { + @Override + public void run() { + ((jogamp.newt.DisplayImpl) newtDisplay).dispatchMessages(); + } }; + this.nedt = new NEDT(threadGroup, name); + this.nedt.setDaemon(true); // don't stop JVM from shutdown .. + } + + @Override + public long getPollPeriod() { + return pollPeriod; + } + + @Override + public void setPollPeriod(final long ms) { + pollPeriod = ms; // writing to static field is intended + } + + @Override + public final void start() throws IllegalStateException { + synchronized(edtLock) { + if( nedt.isRunning() ) { + final Thread curT = Thread.currentThread(); + final boolean onJFXEDT = JFXAccessor.isJFXThread(); + throw new IllegalStateException("EDT still running and not subject to stop. Curr "+curT.getName()+ + ", NEDT "+nedt.getName()+", isRunning "+nedt.isRunning+", shouldStop "+nedt.shouldStop+", JFX-EDT "+JFXAccessor.getJFXThreadName()+", on JFX-EDT "+onJFXEDT); + } + if(DEBUG) { + System.err.println(Thread.currentThread()+": JFX-EDT reset - edt: "+nedt); + } + if( !JFXAccessor.hasJFXThreadStopped() ) { + if( nedt.getState() != Thread.State.NEW ) { + nedt = new NEDT(threadGroup, name); + nedt.setDaemon(true); // don't stop JVM from shutdown .. + } + startImpl(); + } + } + if( !JFXAccessor.hasJFXThreadStopped() ) { + if( !nedt.isRunning() ) { + throw new RuntimeException("EDT could not be started: "+nedt); + } + } else { + // FIXME: Throw exception ? + } + } + + private final void startImpl() { + if(nedt.isAlive()) { + throw new RuntimeException("JFX-EDT Thread.isAlive(): true, isRunning: "+nedt.isRunning+", shouldStop "+nedt.shouldStop+", edt: "+nedt); + } + start_iter++; + nedt.setName(name+start_iter); + if(DEBUG) { + System.err.println(Thread.currentThread()+": JFX-EDT START - edt: "+nedt+", jfxThread "+JFXAccessor.getJFXThreadName()); + // Thread.dumpStack(); + } + nedt.start(); + } + + @Override + public boolean isCurrentThreadEDT() { + return JFXAccessor.isJFXThread(); + } + + @Override + public final boolean isCurrentThreadNEDT() { + return nedt == Thread.currentThread(); + } + + @Override + public final boolean isCurrentThreadEDTorNEDT() { + return JFXAccessor.isJFXThread() || Thread.currentThread() == nedt ; + } + + @Override + public boolean isRunning() { + return nedt.isRunning(); + } + + @Override + public final boolean invokeStop(final boolean wait, final Runnable task) { + return invokeImpl(wait, task, true); + } + + @Override + public final boolean invoke(final boolean wait, final Runnable task) { + return invokeImpl(wait, task, false); + } + + private static Runnable nullTask = new Runnable() { + @Override + public void run() { } + }; + + private final boolean invokeImpl(boolean wait, final Runnable task, boolean stop) { + final RunnableTask rTask; + final Object rTaskLock = new Object(); + synchronized(rTaskLock) { // lock the optional task execution + synchronized(edtLock) { // lock the EDT status + if( nedt.shouldStop ) { + // drop task .. + if(DEBUG) { + System.err.println(Thread.currentThread()+": Warning: JFX-EDT about (1) to stop, won't enqueue new task: "+nedt+", isRunning "+nedt.isRunning+", shouldStop "+nedt.shouldStop); + ExceptionUtils.dumpStack(System.err); + } + return false; + } + final boolean hasJFXThreadStopped = JFXAccessor.hasJFXThreadStopped(); + + if( hasJFXThreadStopped ) { + stop = true; + } + + if( isCurrentThreadEDT() ) { + if(null != task) { + task.run(); + } + wait = false; // running in same thread (EDT) -> no wait + rTask = null; + if( stop ) { + nedt.shouldStop = true; + } + } else { + if( !nedt.isRunning && !hasJFXThreadStopped ) { + if( null != task ) { + if( stop ) { + System.err.println(Thread.currentThread()+": Warning: JFX-EDT is about (3) to stop and stopped already, dropping task. NEDT "+nedt); + } else { + System.err.println(Thread.currentThread()+": Warning: JFX-EDT is not running, dropping task. NEDT "+nedt); + } + if(DEBUG) { + ExceptionUtils.dumpStack(System.err); + } + } + return false; + } else if( stop ) { + if( nedt.isRunning ) { + if(DEBUG) { + System.err.println(Thread.currentThread()+": JFX-EDT signal STOP (on edt: "+isCurrentThreadEDT()+") - "+nedt+", isRunning "+nedt.isRunning+", shouldStop "+nedt.shouldStop); + } + synchronized(nedt.sync) { + nedt.shouldStop = true; + nedt.sync.notifyAll(); // stop immediate if waiting (poll freq) + } + } + if( JFXAccessor.hasJFXThreadStopped() ) { + System.err.println(Thread.currentThread()+": Warning: JFX-EDT is about (3) to stop and stopped already, dropping task. "+nedt); + if(DEBUG) { + ExceptionUtils.dumpStack(System.err); + } + return false; + } + } + + if( null != task ) { + rTask = new RunnableTask(task, + wait ? rTaskLock : null, + true /* always catch and report Exceptions, don't disturb EDT */, + wait ? null : System.err); + Platform.runLater(rTask); + } else { + wait = false; + rTask = null; + } + } + } + if( wait ) { + try { + while( rTask.isInQueue() ) { + rTaskLock.wait(); // free lock, allow execution of rTask + } + } catch (final InterruptedException ie) { + throw new InterruptedRuntimeException(ie); + } + final Throwable throwable = rTask.getThrowable(); + if(null!=throwable) { + if(throwable instanceof NativeWindowException) { + throw (NativeWindowException)throwable; + } + throw new RuntimeException(throwable); + } + } + return true; + } + } + + @Override + final public boolean waitUntilIdle() { + final NEDT _nedt; + synchronized(edtLock) { + _nedt = nedt; + } + if( !_nedt.isRunning || Thread.currentThread() == _nedt || JFXAccessor.hasJFXThreadStopped() || JFXAccessor.isJFXThread() ) { + return false; + } + JFXAccessor.runOnJFXThread(true, nullTask); + return true; + } + + @Override + final public boolean waitUntilStopped() { + synchronized(edtLock) { + final Thread curT = Thread.currentThread(); + final boolean onJFXEDT = JFXAccessor.isJFXThread(); + if( nedt.isRunning && nedt != curT && !onJFXEDT ) { + try { + while( nedt.isRunning ) { + edtLock.wait(); + } + } catch (final InterruptedException e) { + throw new InterruptedRuntimeException(e); + } + return true; + } else { + return false; + } + } + } + + class NEDT extends InterruptSource.Thread { + volatile boolean shouldStop = false; + volatile boolean isRunning = false; + Object sync = new Object(); + + public NEDT(final ThreadGroup tg, final String name) { + super(tg, null, name); + } + + final public boolean isRunning() { + return isRunning && !shouldStop; + } + + @Override + final public void start() throws IllegalThreadStateException { + isRunning = true; + super.start(); + } + + /** + * Utilizing locking only on tasks and its execution, + * not for event dispatching. + */ + @Override + final public void run() { + if(DEBUG) { + System.err.println(getName()+": JFX-EDT run() START "+ getName()); + } + RuntimeException error = null; + try { + do { + // event dispatch + if(!shouldStop) { + // EDT invoke thread is JFX-EDT, + // hence dispatching is required to run on JFX-EDT as well. + // Otherwise a deadlock may happen due to dispatched event's + // triggering a locking action. + JFXAccessor.runOnJFXThread(true, dispatchMessages); + } + // wait + synchronized(sync) { + if(!shouldStop) { + try { + sync.wait(pollPeriod); + } catch (final InterruptedException e) { + throw new InterruptedRuntimeException(e); + } + } + } + } while(!shouldStop) ; + } catch (final Throwable t) { + // handle errors .. + shouldStop = true; + if(t instanceof RuntimeException) { + error = (RuntimeException) t; + } else { + error = new RuntimeException("Within JFX-EDT", t); + } + } finally { + if(DEBUG) { + System.err.println(getName()+": JFX-EDT run() END "+ getName()+", "+error); + } + synchronized(edtLock) { + isRunning = false; + edtLock.notifyAll(); + } + if(DEBUG) { + System.err.println(getName()+": JFX-EDT run() EXIT "+ getName()+", exception: "+error); + } + if(null!=error) { + throw error; + } + } // finally + } // run() + } // EventDispatchThread + +} diff --git a/src/newt/native/X11Common.h b/src/newt/native/X11Common.h index f9254ab76..99795b6e8 100644 --- a/src/newt/native/X11Common.h +++ b/src/newt/native/X11Common.h @@ -42,6 +42,7 @@ #include <X11/Xutil.h> #include <X11/keysym.h> #include <X11/Xatom.h> +#include <X11/extensions/XInput2.h> #include <X11/extensions/Xrandr.h> @@ -74,6 +75,14 @@ extern jmethodID visibleChangedID; extern jmethodID insetsVisibleChangedID; typedef struct { + int id; + int x; + int y; +} XITouchPosition; + +#define XI_TOUCHCOORD_COUNT 10 + +typedef struct { Window window; jobject jwindow; Atom * allAtoms; @@ -85,6 +94,9 @@ typedef struct { Bool maxVert; /** flag whether window is mapped */ Bool isMapped; + int xiOpcode; + int xiTouchDeviceId; + XITouchPosition xiTouchCoords[XI_TOUCHCOORD_COUNT]; } JavaWindow; JavaWindow * getJavaWindowProperty(JNIEnv *env, Display *dpy, Window window, jlong javaObjectAtom, Bool showWarning); diff --git a/src/newt/native/X11Display.c b/src/newt/native/X11Display.c index 32e0f8786..1f3388fff 100644 --- a/src/newt/native/X11Display.c +++ b/src/newt/native/X11Display.c @@ -56,6 +56,7 @@ static jmethodID sendMouseEventRequestFocusID = NULL; static jmethodID visibleChangedWindowRepaintID = NULL; static jmethodID visibleChangedSendMouseEventID = NULL; static jmethodID sizePosMaxInsetsVisibleChangedID = NULL; +static jmethodID sendTouchScreenEventID = NULL; /** * Keycode @@ -266,6 +267,7 @@ JNIEXPORT jboolean JNICALL Java_jogamp_newt_driver_x11_DisplayDriver_initIDs0 sendMouseEventID = (*env)->GetMethodID(env, X11NewtWindowClazz, "sendMouseEvent", "(SIIISF)V"); sendMouseEventRequestFocusID = (*env)->GetMethodID(env, X11NewtWindowClazz, "sendMouseEventRequestFocus", "(SIIISF)V"); visibleChangedSendMouseEventID = (*env)->GetMethodID(env, X11NewtWindowClazz, "visibleChangedSendMouseEvent", "(ZISIIISF)V"); + sendTouchScreenEventID = (*env)->GetMethodID(env, X11NewtWindowClazz, "sendTouchScreenEvent", "(SII[I[I[I[FF)V"); sendKeyEventID = (*env)->GetMethodID(env, X11NewtWindowClazz, "sendKeyEvent", "(SISSCLjava/lang/String;)V"); if (displayCompletedID == NULL || @@ -286,11 +288,11 @@ JNIEXPORT jboolean JNICALL Java_jogamp_newt_driver_x11_DisplayDriver_initIDs0 sendMouseEventID == NULL || sendMouseEventRequestFocusID == NULL || visibleChangedSendMouseEventID == NULL || + sendTouchScreenEventID == NULL || sendKeyEventID == NULL) { return JNI_FALSE; } - return JNI_TRUE; } @@ -385,6 +387,59 @@ static int NewtWindows_updateVisibility(JNIEnv *env, Display *dpy, JavaWindow *j return visibleChange; } +static void sendTouchScreenEvent(JNIEnv *env, JavaWindow *jw, + short eventType, // MouseEvent.EVENT_MOUSE_PRESSED, MouseEvent.EVENT_MOUSE_RELEASED, MouseEvent.EVENT_MOUSE_MOVED + int modifiers, // 0! + int actionId) // index of multiple-pointer arrays representing the pointer which triggered the event +{ + jint pointerNames[XI_TOUCHCOORD_COUNT]; + jint x[XI_TOUCHCOORD_COUNT]; + jint y[XI_TOUCHCOORD_COUNT]; + jfloat pressure[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + jint actionIdx = -1; + int i, cnt; + + for(i = 0, cnt = 0; i < XI_TOUCHCOORD_COUNT; i++) { + if( -1 != jw->xiTouchCoords[i].id ) { + x[cnt] = jw->xiTouchCoords[i].x; + y[cnt] = jw->xiTouchCoords[i].y; + pointerNames[cnt] = jw->xiTouchCoords[i].id; + if (jw->xiTouchCoords[i].id == actionId) { + actionIdx = cnt; + } + cnt++; + } + } + + jintArray jNames = (*env)->NewIntArray(env, cnt); + if (jNames == NULL) { + NewtCommon_throwNewRuntimeException(env, "Could not allocate int array (names) of size %d", cnt); + } + (*env)->SetIntArrayRegion(env, jNames, 0, cnt, pointerNames); + + jintArray jX = (*env)->NewIntArray(env, cnt); + if (jX == NULL) { + NewtCommon_throwNewRuntimeException(env, "Could not allocate int array (x) of size %d", cnt); + } + (*env)->SetIntArrayRegion(env, jX, 0, cnt, x); + + jintArray jY = (*env)->NewIntArray(env, cnt); + if (jY == NULL) { + NewtCommon_throwNewRuntimeException(env, "Could not allocate int array (y) of size %d", cnt); + } + (*env)->SetIntArrayRegion(env, jY, 0, cnt, y); + + jfloatArray jPressure = (*env)->NewFloatArray(env, cnt); + if (jPressure == NULL) { + NewtCommon_throwNewRuntimeException(env, "Could not allocate float array (pressure) of size %d", cnt); + } + (*env)->SetFloatArrayRegion(env, jPressure, 0, cnt, pressure); + + (*env)->CallVoidMethod(env, jw->jwindow, sendTouchScreenEventID, + (jshort)eventType, (jint)modifiers, (jint)actionIdx, + jNames, jX, jY, jPressure, (jfloat)1.0f); +} + /* * Class: jogamp_newt_driver_x11_DisplayDriver * Method: DispatchMessages0 @@ -451,9 +506,10 @@ JNIEXPORT void JNICALL Java_jogamp_newt_driver_x11_DisplayDriver_DispatchMessage continue; } - // DBG_PRINT( "X11: DispatchMessages dpy %p, win %p, Event %d\n", (void*)dpy, (void*)evt.xany.window, (int)evt.type); + Window windowPointer = evt.xany.window; + // DBG_PRINT( "X11: DispatchMessages dpy %p, win %p, Event %d\n", (void*)dpy, (void*)windowPointer, (int)evt.type); - jw = getJavaWindowProperty(env, dpy, evt.xany.window, javaObjectAtom, + jw = getJavaWindowProperty(env, dpy, windowPointer, javaObjectAtom, #ifdef VERBOSE_ON True #else @@ -462,10 +518,59 @@ JNIEXPORT void JNICALL Java_jogamp_newt_driver_x11_DisplayDriver_DispatchMessage ); if(NULL==jw) { - fprintf(stderr, "Warning: NEWT X11 DisplayDispatch %p, Couldn't handle event %d for X11 window %p\n", (void*)dpy, evt.type, (void*)evt.xany.window); + fprintf(stderr, "Warning: NEWT X11 DisplayDispatch %p, Couldn't handle event %d for X11 window %p\n", (void*)dpy, evt.type, (void*)windowPointer); continue; } - + + XGenericEventCookie *cookie = &evt.xcookie; // hacks: https://keithp.com/blogs/Cursor_tracking/ + + if ( GenericEvent == cookie->type && jw->xiOpcode == cookie->extension && XGetEventData(dpy, cookie) ) { + // Valid registered XI Event w/ cookie data + // Here: https://www.x.org/wiki/Development/Documentation/Multitouch/ + XIDeviceEvent *devev = cookie->data; + if( devev->event != windowPointer ) { + DBG_PRINT( "X11: DispatchMessages.XI dpy %p, win %p, Event %d: Event Window %p not matching\n", (void*)dpy, (void*)windowPointer, (int)evt.type, (void*)devev->event); + } else if( devev->deviceid != jw->xiTouchDeviceId) { + DBG_PRINT( "X11: DispatchMessages.XI dpy %p, win %p, Event %d: DeviceID not matching: Window %d, this %d\n", (void*)dpy, (void*)windowPointer, (int)evt.type, devev->deviceid, jw->xiTouchDeviceId); + } else { + int i; + switch (devev->evtype) { + case XI_TouchBegin: + for (i = 0; i < XI_TOUCHCOORD_COUNT; i++) { + if (jw->xiTouchCoords[i].id == -1) { + jw->xiTouchCoords[i].id = devev->detail % 32767; + jw->xiTouchCoords[i].x = devev->event_x; + jw->xiTouchCoords[i].y = devev->event_y; + break; + } + } + sendTouchScreenEvent(env, jw, EVENT_MOUSE_PRESSED, 0, devev->detail % 32767); + break; + + case XI_TouchUpdate: + for (i = 0; i < XI_TOUCHCOORD_COUNT; i++) { + if (jw->xiTouchCoords[i].id == devev->detail % 32767) { + jw->xiTouchCoords[i].x = devev->event_x; + jw->xiTouchCoords[i].y = devev->event_y; + } + } + sendTouchScreenEvent(env, jw, EVENT_MOUSE_MOVED, 0, devev->detail % 32767); + break; + + case XI_TouchEnd: + sendTouchScreenEvent(env, jw, EVENT_MOUSE_RELEASED, 0, devev->detail % 32767); + for (i = 0; i < XI_TOUCHCOORD_COUNT; i++) { + if (jw->xiTouchCoords[i].id == devev->detail % 32767) { + jw->xiTouchCoords[i].id = -1; + } + } + break; + } + } + XFreeEventData(dpy, cookie); + continue; // next event, skip evt.type below + } + switch(evt.type) { case KeyRelease: if (XEventsQueued(dpy, QueuedAfterReading)) { diff --git a/src/newt/native/X11Window.c b/src/newt/native/X11Window.c index de2bddc86..8fb3ecbb9 100644 --- a/src/newt/native/X11Window.c +++ b/src/newt/native/X11Window.c @@ -952,6 +952,62 @@ JNIEXPORT jlongArray JNICALL Java_jogamp_newt_driver_x11_WindowDriver_CreateWind NewtWindows_setMinMaxSize(dpy, javaWindow, width, height, width, height); } } + + // Register X11 Multitouch Events for new Window + // https://www.x.org/wiki/Development/Documentation/Multitouch/ + { + int xi_opcode, event, error; + + javaWindow->xiTouchDeviceId = -1; + + if( XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error) ) { + XIDeviceInfo *di; + int devid = -1; + int cnt = 0; + + javaWindow->xiOpcode = xi_opcode; + di = XIQueryDevice(dpy, XIAllDevices, &cnt); + + if( NULL != di && 0 < cnt ) { + XIDeviceInfo *dev; + int i, j; + + // find the 1st XITouchClass device available + for (i = 0; i < cnt && -1 == devid; i ++) { + dev = &di[i]; + for (j = 0; j < dev->num_classes; j ++) { + XITouchClassInfo *class = (XITouchClassInfo*)(dev->classes[j]); + if ( XITouchClass == class->type ) { + devid = dev->deviceid; + break; + } + } + } + XIFreeDeviceInfo(di); + di = NULL; + + if( -1 != devid ) { + // register 1st XITouchClass device if available + XIEventMask mask = { + .deviceid = devid, + .mask_len = XIMaskLen(XI_TouchEnd) // in bytes + }; + + mask.mask = (unsigned char*)calloc(mask.mask_len, sizeof(unsigned char)); + XISetMask(mask.mask, XI_TouchBegin); + XISetMask(mask.mask, XI_TouchUpdate); + XISetMask(mask.mask, XI_TouchEnd); + + XISelectEvents(dpy, window, &mask, 1); + + free(mask.mask); + + javaWindow->xiTouchDeviceId = devid; + } + } + } + } + XFlush(dpy); handles[0] = (jlong)(intptr_t)window; handles[1] = (jlong)(intptr_t)javaWindow; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestOffscreenLayer02NewtCanvasAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestOffscreenLayer02NewtCanvasAWT.java index b052087a8..d49c1e545 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestOffscreenLayer02NewtCanvasAWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestOffscreenLayer02NewtCanvasAWT.java @@ -53,13 +53,23 @@ import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; import com.jogamp.newt.awt.NewtCanvasAWT; import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; import com.jogamp.opengl.test.junit.newt.parenting.NewtAWTReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.opengl.test.junit.util.AWTRobotUtil; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; import com.jogamp.opengl.util.Animator; +/** + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}, and many more, see {@link #main(String[])} + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestOffscreenLayer02NewtCanvasAWT extends UITestCase { static boolean singleBuffer = false; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestVersionSemanticsNOUI.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestVersionSemanticsNOUI.java index 403efdfdf..07d9e2954 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestVersionSemanticsNOUI.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestVersionSemanticsNOUI.java @@ -110,7 +110,7 @@ public class TestVersionSemanticsNOUI extends SingletonJunitCase { currentJar, curVersionNumber, excludes); } - //@Test + @Test public void testVersionV230V23x_00std() throws IllegalArgumentException, IOException, URISyntaxException { final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; @@ -125,7 +125,7 @@ public class TestVersionSemanticsNOUI extends SingletonJunitCase { curVersion.getClass(), currentCL, curVersionNumber, excludesDefault); } - //@Test + @Test public void testVersionV230V23x_01patch() throws IllegalArgumentException, IOException, URISyntaxException { // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; @@ -141,7 +141,7 @@ public class TestVersionSemanticsNOUI extends SingletonJunitCase { curVersion.getClass(), currentCL, curVersionNumber, excludesStereoPackageAndAppletUtils); } - //@Test + @Test public void testVersionV231V23x_01patch() throws IllegalArgumentException, IOException, URISyntaxException { // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; @@ -163,7 +163,7 @@ public class TestVersionSemanticsNOUI extends SingletonJunitCase { testVersions(diffCriteria, Delta.CompatibilityType.BACKWARD_COMPATIBLE_BINARY, "2.3.0", "2.3.2", excludesStereoPackageAndAppletUtils); } - @Test + // @Test public void testVersionV232V24x0() throws IllegalArgumentException, IOException, URISyntaxException { final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.NON_BACKWARD_COMPATIBLE; // final Delta.CompatibilityType expectedCompatibilityType = Delta.CompatibilityType.BACKWARD_COMPATIBLE_USER; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java index 55b318d23..43417c317 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NEWT.java @@ -52,6 +52,7 @@ import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.test.junit.jogl.demos.GLClearOnInitReshape; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; import com.jogamp.opengl.test.junit.jogl.demos.es2.LineSquareXDemoES2; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.nativewindow.NativeWindowFactory; import com.jogamp.nativewindow.ScalableSurface; import com.jogamp.nativewindow.util.Dimension; @@ -76,6 +77,14 @@ import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +/** + * <p> + * The demo code uses {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000} and many more, see {@link #main(String[])} + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestGearsES2NEWT extends UITestCase { static int screenIdx = 0; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NewtCanvasAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NewtCanvasAWT.java index 4e60c4e95..77c4bf8f3 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NewtCanvasAWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2NewtCanvasAWT.java @@ -47,6 +47,7 @@ import com.jogamp.newt.awt.NewtCanvasAWT; import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.event.WindowAdapter; import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; import com.jogamp.opengl.test.junit.util.AWTRobotUtil; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; @@ -54,6 +55,7 @@ import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; import com.jogamp.opengl.test.junit.newt.parenting.NewtAWTReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.nativewindow.ScalableSurface; import com.jogamp.nativewindow.util.Dimension; @@ -73,6 +75,14 @@ import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +/** + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}, and many more, see {@link #main(String[])} + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestGearsES2NewtCanvasAWT extends UITestCase { public enum FrameLayout { None, TextOnBottom, BorderBottom, BorderBottom2, BorderCenter, BorderCenterSurrounded, DoubleBorderCenterSurrounded }; @@ -81,7 +91,7 @@ public class TestGearsES2NewtCanvasAWT extends UITestCase { static int screenIdx = 0; static PointImmutable wpos; static DimensionImmutable wsize, rwsize = null; - static FrameLayout frameLayout = FrameLayout.None; + static FrameLayout frameLayout = FrameLayout.BorderCenterSurrounded; static ResizeBy resizeBy = ResizeBy.Component; static float[] reqSurfacePixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE }; @@ -247,17 +257,21 @@ public class TestGearsES2NewtCanvasAWT extends UITestCase { final GearsES2 demo = new GearsES2(swapInterval); glWindow.addGLEventListener(demo); + final NewtAWTReparentingKeyAdapter newtDemoListener = new NewtAWTReparentingKeyAdapter(frame, newtCanvasAWT, glWindow); + newtDemoListener.quitAdapterEnable(true); + glWindow.addKeyListener(newtDemoListener); + glWindow.addMouseListener(newtDemoListener); + glWindow.addWindowListener(newtDemoListener); + frame.addComponentListener(new ComponentListener() { @Override public void componentResized(final ComponentEvent e) { - NewtAWTReparentingKeyAdapter.setTitle(frame, newtCanvasAWT, glWindow); + newtDemoListener.setTitle(); } - @Override public void componentMoved(final ComponentEvent e) { - NewtAWTReparentingKeyAdapter.setTitle(frame, newtCanvasAWT, glWindow); + newtDemoListener.setTitle(); } - @Override public void componentShown(final ComponentEvent e) { } @@ -280,12 +294,6 @@ public class TestGearsES2NewtCanvasAWT extends UITestCase { } }); - final NewtAWTReparentingKeyAdapter newtDemoListener = new NewtAWTReparentingKeyAdapter(frame, newtCanvasAWT, glWindow); - newtDemoListener.quitAdapterEnable(true); - glWindow.addKeyListener(newtDemoListener); - glWindow.addMouseListener(newtDemoListener); - glWindow.addWindowListener(newtDemoListener); - if( useAnimator ) { animator.add(glWindow); animator.start(); @@ -319,7 +327,7 @@ public class TestGearsES2NewtCanvasAWT extends UITestCase { System.err.println("HiDPI PixelScale: "+reqSurfacePixelScale[0]+"x"+reqSurfacePixelScale[1]+" (req) -> "+ valReqSurfacePixelScale[0]+"x"+valReqSurfacePixelScale[1]+" (val) -> "+ hasSurfacePixelScale1[0]+"x"+hasSurfacePixelScale1[1]+" (has)"); - NewtAWTReparentingKeyAdapter.setTitle(frame, newtCanvasAWT, glWindow); + newtDemoListener.setTitle(); if( null != rwsize ) { Thread.sleep(500); // 500ms delay diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2SimpleNEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2SimpleNEWT.java index 992d3c58e..6aebeb91b 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2SimpleNEWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestGearsES2SimpleNEWT.java @@ -47,7 +47,7 @@ import com.jogamp.opengl.test.junit.util.QuitAdapter; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.PNGPixelRect; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; - +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.nativewindow.ScalableSurface; import com.jogamp.nativewindow.util.Dimension; import com.jogamp.nativewindow.util.DimensionImmutable; @@ -61,6 +61,14 @@ import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +/** + * <p> + * The demo code uses {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000} and using a translucent window {@code -translucent}. + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestGearsES2SimpleNEWT extends UITestCase { static final DimensionImmutable wsize = new Dimension(800, 600); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestLandscapeES2NewtCanvasAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestLandscapeES2NewtCanvasAWT.java index d24113b8d..c5bbecbc7 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestLandscapeES2NewtCanvasAWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/newt/TestLandscapeES2NewtCanvasAWT.java @@ -39,12 +39,14 @@ import com.jogamp.newt.awt.NewtCanvasAWT; import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.event.WindowAdapter; import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.test.junit.jogl.demos.es2.LandscapeES2; import com.jogamp.opengl.test.junit.newt.parenting.NewtAWTReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.nativewindow.util.Dimension; import com.jogamp.nativewindow.util.DimensionImmutable; @@ -57,6 +59,14 @@ import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +/** + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}, and many more, see {@link #main(String[])} + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestLandscapeES2NewtCanvasAWT extends UITestCase { static DimensionImmutable wsize = new Dimension(500, 290); @@ -112,7 +122,7 @@ public class TestLandscapeES2NewtCanvasAWT extends UITestCase { } }); - final NewtAWTReparentingKeyAdapter newtDemoListener = new NewtAWTReparentingKeyAdapter(frame, newtCanvasAWT, glWindow); + final NewtReparentingKeyAdapter newtDemoListener = new NewtAWTReparentingKeyAdapter(frame, newtCanvasAWT, glWindow); newtDemoListener.quitAdapterEnable(true); glWindow.addKeyListener(newtDemoListener); glWindow.addMouseListener(newtDemoListener); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/gl2/newt/TestGearsNEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/gl2/newt/TestGearsNEWT.java index 3422aa091..0ab9308c2 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/gl2/newt/TestGearsNEWT.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/gl2/newt/TestGearsNEWT.java @@ -36,7 +36,7 @@ import com.jogamp.opengl.test.junit.util.QuitAdapter; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.test.junit.jogl.demos.gl2.Gears; - +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLProfile; @@ -47,6 +47,14 @@ import org.junit.Test; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +/** + * <p> + * The demo code uses {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}. + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestGearsNEWT extends UITestCase { static GLProfile glp; diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/javafx/PureJFXApp01.java b/src/test/com/jogamp/opengl/test/junit/jogl/javafx/PureJFXApp01.java new file mode 100644 index 000000000..d2a3b8073 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/javafx/PureJFXApp01.java @@ -0,0 +1,54 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.jogl.javafx; + +import javafx.application.Application; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +public class PureJFXApp01 extends Application { + + @Override public void start(Stage stage) { + Text text = new Text(10, 40, "Pure JFX App 01"); + text.setFont(new Font(40)); + Scene scene = new Scene(new Group(text)); + + stage.setTitle("JavaFX Stage"); + stage.setScene(scene); + stage.sizeToScene(); + stage.show(); + } + + public static void main(String[] args) { + Application.launch(args); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/javafx/TestNewtCanvasJFXGLn.java b/src/test/com/jogamp/opengl/test/junit/jogl/javafx/TestNewtCanvasJFXGLn.java new file mode 100644 index 000000000..27ce49af4 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/javafx/TestNewtCanvasJFXGLn.java @@ -0,0 +1,516 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.jogl.javafx; + +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilities; +import com.jogamp.opengl.GLCapabilitiesImmutable; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLProfile; + +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +import com.jogamp.common.util.RunnableTask; +import com.jogamp.nativewindow.javafx.JFXAccessor; +import com.jogamp.newt.NewtFactory; +import com.jogamp.newt.Screen; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; +import com.jogamp.newt.javafx.NewtCanvasJFX; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; +import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +import com.jogamp.opengl.test.junit.jogl.demos.es2.MultisampleDemoES2; +import com.jogamp.opengl.test.junit.newt.parenting.NewtJFXReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.util.AWTRobotUtil; +import com.jogamp.opengl.test.junit.util.MiscUtils; +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.GLReadBufferUtil; +import com.jogamp.opengl.util.texture.TextureIO; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +/** + * {@link NewtCanvasJFX} basic functional integration test + * of its native parented NEWT child {@link GLWindow} attached to JavaFX's {@link Canvas}. + * <p> + * {@link NewtCanvasJFX} allows utilizing custom {@link GLCapabilities} settings independent from the JavaFX's window + * as well as independent rendering from JavaFX's thread. + * </p> + * <p> + * This unit tests also tests {@link NewtCanvasJFX} native parenting operations before and after + * it's belonging Group's Scene has been attached to the JavaFX {@link javafx.stage.Window Window}'s actual native window, + * i.e. becoming fully realized and visible. + * </p> + * <p> + * Note that {@link JFXAccessor#runOnJFXThread(boolean, Runnable)} is still used to for certain + * mandatory JavaFX lifecycle operation on the JavaFX thread. + * </p> + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows running a single test, e.g. {@code -test 21}, and setting each tests's duration in milliseconds, e.g.{@code -time 10000}. + * </p> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestNewtCanvasJFXGLn extends UITestCase { + + static int duration = 5000; // 250; + static int manualTestID = -1; + + com.jogamp.newt.Display jfxNewtDisplay = null; + + public static class JFXApp extends Application { + static Stage stage; + + final static Object sync = new Object(); + static volatile boolean isLaunched = false; + + public JFXApp() { + } + + @Override public void init() throws Exception { + // pre JFX thread + System.err.println("JFX init ...: "+Thread.currentThread()); + } + + @Override public void start(final Stage stage) { + System.err.println("JFX start.0 ...: "+Thread.currentThread()); + synchronized(sync) { + try { + // on JFX thread + final Scene scene = new Scene(new Group(), defWidth, defHeight); + stage.setTitle(TestNewtCanvasJFXGLn.class.getSimpleName()); + stage.setScene(scene); + stage.sizeToScene(); + { + final long h = JFXAccessor.getWindowHandle(stage); + System.err.println("t1 - Native window: 0x"+Long.toHexString(h)); + } + stage.show(); + { + final long h = JFXAccessor.getWindowHandle(stage); + System.err.println("t2 - Native window: 0x"+Long.toHexString(h)); + } + JFXApp.stage = stage; + } finally { + isLaunched = true; + sync.notifyAll(); + } + } + System.err.println("JFX start.X ...: "+Thread.currentThread()); + } + @Override public void stop() throws Exception { + System.err.println("JFX stop ...: "+Thread.currentThread()); + } + public static void startup() throws InterruptedException { + System.out.println( "GLProfile " + GLProfile.glAvailabilityToString() ); + System.err.println("JFX Available: "+JFXAccessor.isJFXAvailable()); + if( JFXAccessor.isJFXAvailable() ) { + Platform.setImplicitExit(false); // FIXME: Default for all NEWT cases? + synchronized(sync) { + final Thread ct = Thread.currentThread(); + RunnableTask.invokeOnNewThread(ct.getThreadGroup(), ct.getName()+"JFXLauncher", false, + new Runnable() { + public void run() { + Application.launch(JFXApp.class); + } + }); + while(!isLaunched) { + sync.wait(); + } + } + System.err.println("JFX launched ..."); + } + } + public static void shutdown() { + JFXAccessor.runOnJFXThread(true, new Runnable() { + public void run() { + if( null != stage ) { + stage.close(); + } + } }); + } + } + + @BeforeClass + public static void startup() throws InterruptedException { + JFXApp.startup(); + } + + @AfterClass + public static void shutdown() { + JFXApp.shutdown(); + Platform.exit(); + } + + @Before + public void init() { + jfxNewtDisplay = NewtFactory.createDisplay(null, false); // no-reuse + } + + @After + public void release() { + jfxNewtDisplay = null; + } + + class WaitAction implements Runnable { + private final long sleepMS; + + WaitAction(final long sleepMS) { + this.sleepMS = sleepMS; + } + public void run() { + // blocks on linux .. display.sleep(); + try { + Thread.sleep(sleepMS); + } catch (final InterruptedException e) { } + } + } + final WaitAction awtRobotWaitAction = new WaitAction(AWTRobotUtil.TIME_SLICE); + final WaitAction generalWaitAction = new WaitAction(10); + + static final int defWidth = 800, defHeight = 600; + + static void populateScene(final Scene scene, final boolean postAttach, + final GLWindow glWindow, + final int width, final int height, final boolean useBorder, + final NewtCanvasJFX[] res) { + final javafx.stage.Window w = scene.getWindow(); + final boolean isShowing = null != w && w.isShowing(); + final Group g = new Group(); + + final int cx, cy, cw, ch, bw, bh; + if( useBorder ) { + bw = width/5; bh = height/5; + cx = bw; cy = bh; cw = width-bw-bw; ch = height-bh-bh; + } else { + bw = 0; bh = 0; + cx = 0; cy = 0; cw = width; ch = height; + } + System.err.println("Scene "+width+"x"+height+", isShowing "+isShowing+", postAttach "+postAttach); + System.err.println("Scene.canvas "+cx+"/"+cy+" "+cw+"x"+ch); + System.err.println("Scene.border "+bw+"x"+bh); + + if( !postAttach ) { + if(isShowing) { + JFXAccessor.runOnJFXThread(true, new Runnable() { + @Override + public void run() { + scene.setRoot(g); + }}); + } else { + scene.setRoot(g); + } + } + + final Canvas canvas0; + if( null == res ) { + canvas0 = new Canvas(); + } else { + res[0] = new NewtCanvasJFX( glWindow ); + canvas0 = res[0]; + } + canvas0.setWidth(cw); + canvas0.setHeight(ch); + if( null == res ) { + final GraphicsContext gc = canvas0.getGraphicsContext2D(); + gc.setFill(Color.BLUE); + gc.fillRect(0, 0, cw, ch); + } + canvas0.relocate(cx, cy); + + final Text text0 = new Text(0, 0, "left"); + { + text0.setFont(new Font(40)); + text0.relocate(0, height/2); + } + final Text text1 = new Text(0, 0, "above"); + { + text1.setFont(new Font(40)); + text1.relocate(width/2, bh-40); + } + final Text text2 = new Text(0, 0, "right"); + { + text2.setFont(new Font(40)); + text2.relocate(width-bw, height/2); + } + final Text text3 = new Text(0, 0, "below"); + { + text3.setFont(new Font(40)); + text3.relocate(width/2, height-bh); + } + final Runnable attach2Group = new Runnable() { + @Override + public void run() { + g.getChildren().add(text0); + g.getChildren().add(text1); + g.getChildren().add(canvas0); + g.getChildren().add(text2); + g.getChildren().add(text3); + } }; + if( !postAttach && isShowing ) { + JFXAccessor.runOnJFXThread(true, attach2Group); + } else { + attach2Group.run(); + } + if( postAttach ) { + if(isShowing) { + JFXAccessor.runOnJFXThread(true, new Runnable() { + @Override + public void run() { + scene.setRoot(g); + }}); + } else { + scene.setRoot(g); + } + } + } + + protected void runTestAGL( final GLCapabilitiesImmutable caps, final GLEventListener demo, + final boolean postAttachNewtCanvas, final boolean postAttachGLWindow, + final boolean useAnimator ) throws InterruptedException { + if( !JFXAccessor.isJFXAvailable() ) { + System.err.println("JFX not available"); + return; + } + final GLReadBufferUtil screenshot = new GLReadBufferUtil(false, false); + final GLWindow glWindow1; + if( null == demo ) { + glWindow1 = null; + } else { + final Screen screen = NewtFactory.createScreen(jfxNewtDisplay, 0); + glWindow1 = GLWindow.create(screen, caps); + Assert.assertNotNull(glWindow1); + Assert.assertEquals(false, glWindow1.isVisible()); + Assert.assertEquals(false, glWindow1.isNativeValid()); + Assert.assertNull(glWindow1.getParent()); + glWindow1.addGLEventListener(demo); + glWindow1.addGLEventListener(new GLEventListener() { + int displayCount = 0; + public void init(final GLAutoDrawable drawable) { } + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { } + public void display(final GLAutoDrawable drawable) { + if(displayCount < 3) { + snapshot(displayCount++, null, drawable.getGL(), screenshot, TextureIO.PNG, null); + } + } + public void dispose(final GLAutoDrawable drawable) { } + }); + } + + final NewtCanvasJFX[] glCanvas = null==demo? null : new NewtCanvasJFX[]{null}; + + final Scene scene = new Scene(new Group(), defWidth, defHeight); + if(!postAttachNewtCanvas) { + System.err.println("Stage set.A0"); + JFXAccessor.runOnJFXThread(true, new Runnable() { + public void run() { + System.err.println("Stage set.A1"); + JFXApp.stage.setScene(scene); + JFXApp.stage.sizeToScene(); + System.err.println("Stage set.AX"); + } }); + } + populateScene( scene, postAttachNewtCanvas, postAttachGLWindow?null:glWindow1, defWidth, defHeight, true, glCanvas); + if(postAttachNewtCanvas) { + System.err.println("Stage set.B0"); + JFXAccessor.runOnJFXThread(true, new Runnable() { + public void run() { + System.err.println("Stage set.B1"); + JFXApp.stage.setScene(scene); + JFXApp.stage.sizeToScene(); + System.err.println("Stage set.BX"); + } }); + } + + if(postAttachGLWindow && null != demo) { + glCanvas[0].setNEWTChild(glWindow1); + } + + if( null != glWindow1 ) { + Assert.assertTrue("GLWindow didn't become visible natively!", AWTRobotUtil.waitForRealized(glWindow1, awtRobotWaitAction, true)); + System.err.println("GLWindow LOS.0: "+glWindow1.getLocationOnScreen(null)); + glWindow1.addWindowListener(new WindowAdapter() { + public void windowResized(final WindowEvent e) { + System.err.println("window resized: "+glWindow1.getX()+"/"+glWindow1.getY()+" "+glWindow1.getSurfaceWidth()+"x"+glWindow1.getSurfaceHeight()); + } + public void windowMoved(final WindowEvent e) { + System.err.println("window moved: "+glWindow1.getX()+"/"+glWindow1.getY()+" "+glWindow1.getSurfaceWidth()+"x"+glWindow1.getSurfaceHeight()); + } + }); + final NewtReparentingKeyAdapter newtDemoListener = new NewtJFXReparentingKeyAdapter(JFXApp.stage, glCanvas[0], glWindow1); + newtDemoListener.quitAdapterEnable(true); + glWindow1.addKeyListener(newtDemoListener); + glWindow1.addMouseListener(newtDemoListener); + glWindow1.addWindowListener(newtDemoListener); + + final ChangeListener<Number> sizeListener = new ChangeListener<Number>() { + @Override public void changed(final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) { + newtDemoListener.setTitle(); + } }; + JFXApp.stage.widthProperty().addListener(sizeListener); + JFXApp.stage.heightProperty().addListener(sizeListener); + + } + if( null != demo ) { + System.err.println("NewtCanvasJFX LOS.0: "+glCanvas[0].getNativeWindow().getLocationOnScreen(null)); + } + + Animator anim; + if(useAnimator && null != demo) { + anim = new Animator(glWindow1); + anim.start(); + } else { + anim = null; + } + + final long lStartTime = System.currentTimeMillis(); + final long lEndTime = lStartTime + duration; + try { + while( (System.currentTimeMillis() < lEndTime) ) { + generalWaitAction.run(); + } + } catch( final Throwable throwable ) { + throwable.printStackTrace(); + Assume.assumeNoException( throwable ); + } + if(null != anim) { + anim.stop(); + } + + JFXAccessor.runOnJFXThread(true, new Runnable() { + public void run() { + populateScene( JFXApp.stage.getScene(), false, null, defWidth, defHeight, true, null); + JFXApp.stage.sizeToScene(); + } }); + } + + @Test + public void test00() throws InterruptedException { + if( 0 > manualTestID || 0 == manualTestID ) { + runTestAGL( null, null, + false /* postAttachNewtCanvas */, false /* postAttach */, false /* animator */); + } + } + + @Test + public void test11_preAttachNewtGL_NoAnim() throws InterruptedException { + if( 0 > manualTestID || 11 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + false /* postAttachNewtCanvas */, false /* postAttachGLWindow */, false /* animator */); + } + } + + @Test + public void test12_postAttachNewt_NoAnim() throws InterruptedException { + if( 0 > manualTestID || 12 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + true /* postAttachNewtCanvas */, false /* postAttachGLWindow */, false /* animator */); + } + } + + @Test + public void test13_postAttachGL_NoAnim() throws InterruptedException { + if( 0 > manualTestID || 13 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + false /* postAttachNewtCanvas */, true /* postAttachGLWindow */, false /* animator */); + } + } + + @Test + public void test14_postAttachNewtGL_NoAnim() throws InterruptedException { + if( 0 > manualTestID || 14 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + true /* postAttachNewtCanvas */, true /* postAttachGLWindow */, false /* animator */); + } + } + + @Test + public void test21_preAttachNewtGL_DoAnim() throws InterruptedException { + if( 0 > manualTestID || 21 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + false /* postAttachNewtCanvas */, false /* postAttachGLWindow */, true /* animator */); + } + } + + @Test + public void test22_postAttachNewt_DoAnim() throws InterruptedException { + if( 0 > manualTestID || 22 == manualTestID ) { + runTestAGL( new GLCapabilities(GLProfile.getGL2ES2()), new GearsES2(), + true /* postAttachNewtCanvas */, false /* postAttachGLWindow */, true /* animator */); + } + } + + @Test + public void test30_MultisampleAndAlpha() throws InterruptedException { + if( 0 > manualTestID || 30 == manualTestID ) { + final GLCapabilities caps = new GLCapabilities(GLProfile.getGL2ES2()); + caps.setSampleBuffers(true); + caps.setNumSamples(2); + runTestAGL( caps, new MultisampleDemoES2(true), + false /* postAttachNewtCanvas */, false /* postAttachGLWindow */, false /* animator */); + } + } + + public static void main(final String args[]) { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-time")) { + duration = MiscUtils.atoi(args[++i], duration); + } + if(args[i].equals("-test")) { + manualTestID = MiscUtils.atoi(args[++i], -1); + } + } + System.out.println("durationPerTest: "+duration+", test "+manualTestID); + org.junit.runner.JUnitCore.main(TestNewtCanvasJFXGLn.class.getName()); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/newt/event/TestParentingFocus03KeyTraversalAWT.java b/src/test/com/jogamp/opengl/test/junit/newt/event/TestParentingFocus03KeyTraversalAWT.java index b9be0ad9a..283fc262a 100644 --- a/src/test/com/jogamp/opengl/test/junit/newt/event/TestParentingFocus03KeyTraversalAWT.java +++ b/src/test/com/jogamp/opengl/test/junit/newt/event/TestParentingFocus03KeyTraversalAWT.java @@ -53,6 +53,7 @@ import com.jogamp.opengl.*; import com.jogamp.opengl.util.Animator; import com.jogamp.newt.*; import com.jogamp.newt.opengl.*; +import com.jogamp.newt.opengl.util.NEWTDemoListener; import com.jogamp.newt.awt.NewtCanvasAWT; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; @@ -64,12 +65,19 @@ import jogamp.newt.driver.DriverClearFocus; import com.jogamp.opengl.test.junit.util.*; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; import com.jogamp.opengl.test.junit.newt.parenting.NewtAWTReparentingKeyAdapter; +import com.jogamp.opengl.test.junit.newt.parenting.NewtReparentingKeyAdapter; /** * Testing focus <i>key</i> traversal of an AWT component tree with {@link NewtCanvasAWT} attached. * <p> * {@link Frame} [ Button*, {@link NewtCanvasAWT} . {@link GLWindow} ] * </p> + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}, and many more, see {@link #main(String[])} + * </p> */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestParentingFocus03KeyTraversalAWT extends UITestCase { diff --git a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtAWTReparentingKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtAWTReparentingKeyAdapter.java index 4fdee82c3..7e92c8438 100644 --- a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtAWTReparentingKeyAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtAWTReparentingKeyAdapter.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011, 2019 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: @@ -30,93 +30,37 @@ package com.jogamp.opengl.test.junit.newt.parenting; import java.awt.Frame; import com.jogamp.nativewindow.CapabilitiesImmutable; +import com.jogamp.nativewindow.NativeWindow; +import com.jogamp.nativewindow.NativeWindowHolder; import com.jogamp.nativewindow.util.InsetsImmutable; import com.jogamp.newt.Window; -import com.jogamp.newt.awt.NewtCanvasAWT; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.newt.opengl.util.NEWTDemoListener; -public class NewtAWTReparentingKeyAdapter extends NEWTDemoListener { +/** + * AWT specializing demo functionality of {@link NewtReparentingKeyAdapter}, includes {@link NEWTDemoListener}. + */ +public class NewtAWTReparentingKeyAdapter extends NewtReparentingKeyAdapter { final Frame frame; - final NewtCanvasAWT newtCanvasAWT; - public NewtAWTReparentingKeyAdapter(final Frame frame, final NewtCanvasAWT newtCanvasAWT, final GLWindow glWindow) { - super(glWindow, null); + public NewtAWTReparentingKeyAdapter(final Frame frame, final NativeWindowHolder winHolder, final GLWindow glWindow) { + super(winHolder, glWindow); this.frame = frame; - this.newtCanvasAWT = newtCanvasAWT; - } - - public void keyPressed(final KeyEvent e) { - if( e.isAutoRepeat() || e.isConsumed() ) { - return; - } - if( 0 == e.getModifiers() ) { // all modifiers go to super class .. - final int keySymbol = e.getKeySymbol(); - switch (keySymbol) { - case KeyEvent.VK_L: - e.setConsumed(true); - final com.jogamp.nativewindow.util.Point p0 = newtCanvasAWT.getNativeWindow().getLocationOnScreen(null); - final com.jogamp.nativewindow.util.Point p1 = glWindow.getLocationOnScreen(null); - printlnState("[location]", "AWT "+p0+", NEWT "+p1); - break; - case KeyEvent.VK_R: - e.setConsumed(true); - quitAdapterOff(); - glWindow.invokeOnNewThread(null, false, new Runnable() { - public void run() { - final java.lang.Thread t = glWindow.setExclusiveContextThread(null); - if(glWindow.getParent()==null) { - printlnState("[reparent pre - glWin to HOME]"); - glWindow.reparentWindow(newtCanvasAWT.getNativeWindow(), -1, -1, 0 /* hints */); - } else { - if( null != frame ) { - final InsetsImmutable nInsets = glWindow.getInsets(); - final java.awt.Insets aInsets = frame.getInsets(); - int dx, dy; - if( nInsets.getTotalHeight()==0 ) { - dx = aInsets.left; - dy = aInsets.top; - } else { - dx = nInsets.getLeftWidth(); - dy = nInsets.getTopHeight(); - } - final int topLevelX = frame.getX()+frame.getWidth()+dx; - final int topLevelY = frame.getY()+dy; - printlnState("[reparent pre - glWin to TOP.1]", topLevelX+"/"+topLevelY+" - insets " + nInsets + ", " + aInsets); - glWindow.reparentWindow(null, topLevelX, topLevelY, 0 /* hint */); - } else { - printlnState("[reparent pre - glWin to TOP.0]"); - glWindow.reparentWindow(null, -1, -1, 0 /* hints */); - } - } - printlnState("[reparent post]"); - glWindow.requestFocus(); - glWindow.setExclusiveContextThread(t); - quitAdapterOn(); - } } ); - break; - } - } - super.keyPressed(e); } @Override public void setTitle() { - setTitle(frame, newtCanvasAWT, glWindow); + setTitle(frame, winHolder.getNativeWindow(), glWindow); } - public static void setTitle(final Frame frame, final NewtCanvasAWT glc, final Window win) { + public void setTitle(final Frame frame, final NativeWindow nw, final Window win) { final CapabilitiesImmutable chosenCaps = win.getChosenCapabilities(); final CapabilitiesImmutable reqCaps = win.getRequestedCapabilities(); final CapabilitiesImmutable caps = null != chosenCaps ? chosenCaps : reqCaps; final String capsA = caps.isBackgroundOpaque() ? "opaque" : "transl"; { - final java.awt.Rectangle b = glc.getBounds(); - frame.setTitle("NewtCanvasAWT["+capsA+"], win: ["+b.x+"/"+b.y+" "+b.width+"x"+b.height+"], pix: "+glc.getNativeWindow().getSurfaceWidth()+"x"+glc.getNativeWindow().getSurfaceHeight()); + frame.setTitle("Frame["+capsA+"], win: "+getNativeWinTitle(nw)); } - final float[] sDPI = win.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; - win.setTitle("GLWindow["+capsA+"], win: "+win.getBounds()+", pix: "+win.getSurfaceWidth()+"x"+win.getSurfaceHeight()+", sDPI "+sDPI[0]+" x "+sDPI[1]); + super.setTitle(nw, win); } } diff --git a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtJFXReparentingKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtJFXReparentingKeyAdapter.java new file mode 100644 index 000000000..3ed847ae3 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtJFXReparentingKeyAdapter.java @@ -0,0 +1,102 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.newt.parenting; + +import com.jogamp.nativewindow.CapabilitiesImmutable; +import com.jogamp.nativewindow.NativeWindow; +import com.jogamp.nativewindow.NativeWindowHolder; +import com.jogamp.nativewindow.util.Insets; +import com.jogamp.nativewindow.util.InsetsImmutable; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; + +import javafx.geometry.Bounds; + +/** + * JavaFX specializing demo functionality of {@link NewtReparentingKeyAdapter}, includes {@link NEWTDemoListener}. + */ +public class NewtJFXReparentingKeyAdapter extends NewtReparentingKeyAdapter { + final javafx.stage.Stage frame; + + public NewtJFXReparentingKeyAdapter(final javafx.stage.Stage frame, final NativeWindowHolder winHolder, final GLWindow glWindow) { + super(winHolder, glWindow); + this.frame = frame; + } + + @Override + public void keyPressed(final KeyEvent e) { + if( e.isAutoRepeat() || e.isConsumed() ) { + return; + } + if( 0 == e.getModifiers() ) { // all modifiers go to super class .. + final int keySymbol = e.getKeySymbol(); + switch (keySymbol) { + case KeyEvent.VK_R: + e.setConsumed(true); + quitAdapterOff(); + glWindow.invokeOnNewThread(null, false, new Runnable() { + public void run() { + final java.lang.Thread t = glWindow.setExclusiveContextThread(null); + if(glWindow.getParent()==null) { + printlnState("[reparent pre - glWin to HOME: child pos "+winHolder.getNativeWindow().getX()+"/"+winHolder.getNativeWindow().getY()+"]"); + glWindow.reparentWindow(winHolder.getNativeWindow(), winHolder.getNativeWindow().getX(), winHolder.getNativeWindow().getY(), 0 /* hints */); + glWindow.setPosition(winHolder.getNativeWindow().getX(), winHolder.getNativeWindow().getY()); + } else { + final com.jogamp.nativewindow.util.Point p0 = winHolder.getNativeWindow().getLocationOnScreen(null); + final com.jogamp.nativewindow.util.Point p1 = glWindow.getLocationOnScreen(null); + printlnState("[reparent pre - glWin to TOP.1] frame ", p0+", glWindow "+p1); + glWindow.reparentWindow(null, p1.getX(), p1.getY(), 0 /* hint */); + } + printlnState("[reparent post]"); + glWindow.requestFocus(); + glWindow.setExclusiveContextThread(t); + quitAdapterOn(); + } } ); + break; + } + } + super.keyPressed(e); + } + + @Override + public void setTitle() { + setTitle(frame, winHolder.getNativeWindow(), glWindow); + } + public void setTitle(final javafx.stage.Stage frame, final NativeWindow nw, final Window win) { + final CapabilitiesImmutable chosenCaps = win.getChosenCapabilities(); + final CapabilitiesImmutable reqCaps = win.getRequestedCapabilities(); + final CapabilitiesImmutable caps = null != chosenCaps ? chosenCaps : reqCaps; + final String capsA = caps.isBackgroundOpaque() ? "opaque" : "transl"; + { + frame.setTitle("Frame["+capsA+"], win: "+getNativeWinTitle(nw)); + } + super.setTitle(nw, win); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java new file mode 100644 index 000000000..339230d48 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java @@ -0,0 +1,111 @@ +/** + * Copyright 2011, 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.newt.parenting; + +import com.jogamp.nativewindow.CapabilitiesImmutable; +import com.jogamp.nativewindow.NativeWindow; +import com.jogamp.nativewindow.NativeWindowHolder; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.newt.opengl.util.NEWTDemoListener; +import com.jogamp.opengl.GLAnimatorControl; + +/** + * Extending demo functionality of {@link NEWTDemoListener} + * <ul> + * <li>L: Print parent and (child) {@link GLWindow} location</li> + * <li>R: Toggel parenting (top-level/child)</li> + * </ul> + */ +public class NewtReparentingKeyAdapter extends NEWTDemoListener { + final NativeWindowHolder winHolder; + + public NewtReparentingKeyAdapter(final NativeWindowHolder winHolder, final GLWindow glWindow) { + super(glWindow, null); + this.winHolder = winHolder; + } + + @Override + public void keyPressed(final KeyEvent e) { + if( e.isAutoRepeat() || e.isConsumed() ) { + return; + } + if( 0 == e.getModifiers() ) { // all modifiers go to super class .. + final int keySymbol = e.getKeySymbol(); + switch (keySymbol) { + case KeyEvent.VK_L: + e.setConsumed(true); + final com.jogamp.nativewindow.util.Point p0 = winHolder.getNativeWindow().getLocationOnScreen(null); + final com.jogamp.nativewindow.util.Point p1 = glWindow.getLocationOnScreen(null); + printlnState("[location]", "Parent "+p0+", NEWT "+p1); + break; + case KeyEvent.VK_R: + e.setConsumed(true); + quitAdapterOff(); + glWindow.invokeOnNewThread(null, false, new Runnable() { + public void run() { + final java.lang.Thread t = glWindow.setExclusiveContextThread(null); + if(glWindow.getParent()==null) { + printlnState("[reparent pre - glWin to HOME: child pos "+winHolder.getNativeWindow().getX()+"/"+winHolder.getNativeWindow().getY()+"]"); + glWindow.reparentWindow(winHolder.getNativeWindow(), -1, -1, 0 /* hints */); + } else { + final com.jogamp.nativewindow.util.Point p0 = winHolder.getNativeWindow().getLocationOnScreen(null); + final com.jogamp.nativewindow.util.Point p1 = glWindow.getLocationOnScreen(null); + printlnState("[reparent pre - glWin to TOP.1] frame ", p0+", glWindow "+p1); + glWindow.reparentWindow(null, p1.getX(), p1.getY(), 0 /* hint */); + } + printlnState("[reparent post]"); + glWindow.requestFocus(); + glWindow.setExclusiveContextThread(t); + quitAdapterOn(); + } } ); + break; + } + } + super.keyPressed(e); + } + + @Override + public void setTitle() { + setTitle(winHolder.getNativeWindow(), glWindow); + } + String getNativeWinTitle(final NativeWindow nw) { + return "["+nw.getX()+"/"+nw.getY()+" "+nw.getWidth()+"x"+nw.getHeight()+"], pix: "+nw.getSurfaceWidth()+"x"+nw.getSurfaceHeight(); + } + public void setTitle(final NativeWindow nw, final Window win) { + final CapabilitiesImmutable chosenCaps = win.getChosenCapabilities(); + final CapabilitiesImmutable reqCaps = win.getRequestedCapabilities(); + final CapabilitiesImmutable caps = null != chosenCaps ? chosenCaps : reqCaps; + final String capsA = caps.isBackgroundOpaque() ? "opaque" : "transl"; + final float[] sDPI = win.getPixelsPerMM(new float[2]); + sDPI[0] *= 25.4f; + sDPI[1] *= 25.4f; + win.setTitle("GLWindow["+capsA+"], win: "+win.getBounds()+", pix: "+win.getSurfaceWidth()+"x"+win.getSurfaceHeight()+", sDPI "+sDPI[0]+" x "+sDPI[1]); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/newt/parenting/TestParenting03AWT.java b/src/test/com/jogamp/opengl/test/junit/newt/parenting/TestParenting03AWT.java index 73d41aefd..005839cec 100644 --- a/src/test/com/jogamp/opengl/test/junit/newt/parenting/TestParenting03AWT.java +++ b/src/test/com/jogamp/opengl/test/junit/newt/parenting/TestParenting03AWT.java @@ -47,6 +47,7 @@ import com.jogamp.opengl.*; import com.jogamp.opengl.util.Animator; import com.jogamp.newt.*; import com.jogamp.newt.opengl.*; +import com.jogamp.newt.opengl.util.NEWTDemoListener; import com.jogamp.newt.awt.NewtCanvasAWT; import java.io.IOException; @@ -54,6 +55,14 @@ import java.io.IOException; import com.jogamp.opengl.test.junit.util.*; import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +/** + * <p> + * The demo code uses {@link NewtReparentingKeyAdapter} including {@link NEWTDemoListener} functionality. + * </p> + * <p> + * Manual invocation via main allows setting each tests's duration in milliseconds, e.g.{@code -duration 10000}, and many more, see {@link #main(String[])} + * </p> + */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestParenting03AWT extends UITestCase { static Dimension glSize, fSize; |