/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.ui.swingui; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import org.openmali.FastMath; import org.openmali.vecmath2.Vector3f; import org.xith3d.render.BaseRenderPassConfig; import org.xith3d.render.Canvas3D; import org.xith3d.render.RenderPass; import org.xith3d.scenegraph.BranchGroup; import org.xith3d.scenegraph.Group; import org.xith3d.scenegraph.OrderedGroup; import org.xith3d.scenegraph.Transform3D; import org.xith3d.scenegraph.TransformGroup; import org.xith3d.scenegraph.View; import org.xith3d.utility.logging.X3DLog; /** * The overlay manager keeps track of all the overlay's on the screen and * makes sure they are updated with the view transform once a frame. The * Overlay manager should be placed into the XithBackground in the last * ordered group to ensure that it is rendered last. * <p> * * Originally Coded by David Yazel on Oct 4, 2003 at 11:29:28 PM. */ public class UIWindowManager extends RenderPass { private static float consoleZ = 7f; //private static float consoleZ = 4f; //private static int MAX_OVERLAYS = 20; private static int OM_NONE = 0; private static int OM_MOVE_WINDOW = 1; private static int OM_DRAGGING = 2; private static int OM_DRAGGING_NONE = 3; //private static int OM_DRAGGING_MAYBE = 4; private TransformGroup consoleTG; // transform group for the image plate private Group windows; // un-ordered group of UIPositionedWindow private OrderedGroup orderedWindows; // ordered group of UIPositionedWindow public Transform3D planeOffset; // transform of image plate public Transform3D worldTransform; public Transform3D lastTransform; private ArrayList< UIPositionedWindow > windowList = null; private float consoleWidth; // width of console private float consoleHeight; // height of console private float scale; // scale into world coordinates private Dimension canvasDim; // the dimensions of the Canvas3d private Dimension checkDim; // used to check for dimension changes private int mode = OM_NONE; // the mode we are in with the window private UIPositionedWindow focus; // current focused window. private UIPositionedWindow lastWindow = null; // used for fast checking mouse events private Canvas3D c3d; private boolean isDirectX = false; private int checks = 0; //private long dragStart; // objects needed for drag and drop support private UIDraggingInformation draggingInfo = null; private UIPositionedWindow dragWindow = null; // used for picking into the world and finding objects private boolean mouseClicked = false; /** * @return the first window that contains the specified point. If the * windows are stacked then the top most window will be picked. */ private UIPositionedWindow getWindow( Point p ) { if ( lastWindow != null ) { if ( lastWindow.isVisible() && lastWindow.contains( p ) ) { return ( lastWindow ); } } UIPositionedWindow w = null; int n = windowList.size(); for ( int i = 0; i < n; i++ ) { w = windowList.get( i ); if ( w != dragWindow ) { if ( w.isVisible() && w.contains( p ) ) { lastWindow = w; return ( w ); } } } return ( null ); } public void calcImagePlate() { // get the field of view and then calculate the width in meters of the // screen canvasDim.height = c3d.getHeight(); canvasDim.width = c3d.getWidth(); float aspect = (float)canvasDim.width / (float)canvasDim.height; float fovy = c3d.getView().getFieldOfView(); //float xmin; float xmax; //float ymin; float ymax; ymax = consoleZ * FastMath.tan( fovy ); //ymin = -ymax; //xmin = ymin * aspect; xmax = ymax * aspect; scale = ( ymax * 2 ) / canvasDim.height; consoleWidth = xmax * 2; consoleHeight = ymax * 2; //float screenScale = c3d.getView().getScreenScale(); //consoleHeight = (FastMath.tan( fovy / 2.0 ) * consoleZ) * 2.0f; //consoleWidth = consoleHeight * aspect; //scale = consoleHeight / canvasDim.height; X3DLog.debug( "Building overlay mapping" ); //X3DLog.println( LogType.EXHAUSTIVE, " Field of view up and down is : " + fov + " or "+ FastMath.toDeg( fov )+" degrees" ); //X3DLog.println( LogType.EXHAUSTIVE, " Screen scale : " + screenScale ); //get the canvas width in pixels, then calculate the scale from // pixels to meters. Then calculate the height of the screen in meters //c3d.getSize( canvasDim ); if ( canvasDim.width != 0 ) { // build the plane offset X3DLog.debug( " Overlay scale : ", scale ); X3DLog.debug( " aspect is : ", aspect ); X3DLog.debug( " Screen size is : ", canvasDim ); X3DLog.debug( " console width (meters) : ", consoleWidth ); X3DLog.debug( " console height (meters) : ", consoleHeight ); float texelOffset = 0; if ( isDirectX ) { texelOffset = scale / 2.0f; } X3DLog.debug( " Driver is : ", ( isDirectX ? "Direct X" : "OpenGL" ) ); X3DLog.debug( " Texel Offset : ", texelOffset ); planeOffset.setTranslation( new Vector3f( ( -consoleWidth / 2.0f ) - texelOffset, ( -consoleHeight / 2.0f ) - texelOffset, -consoleZ ) ); planeOffset.setScale( scale ); } else { X3DLog.error( "Cannot calculate image plate" ); } checkDim.setSize( canvasDim ); } /** * Called each frame prior to rendering to update the overlays. * Convenience method, calls <code>newFrame(view.getTransform());</code> * * @param view the view who's transform is used to update the overlays. */ public void newFrame( View view ) { newFrame( view.getTransform() ); } /** * Called once a frame to update the different overlays. */ public void newFrame( Transform3D viewTransform ) { /* if ( !this.isLive() ) { return; } */ if ( windowList.size() == 0 ) { return; } if ( ( checks % 100 ) == 0 ) { //checkScreenSize(); } //Vector3f location = new Vector3f(); worldTransform.set( viewTransform ); //worldTransform.invert(); //worldTransform.get( location ); //Log.log.println( LogType.EXHAUSTIVE, " View location is : " + location ); worldTransform.mul( planeOffset ); consoleTG.setTransform( worldTransform ); // now update all the visible, assigned overlays final int n = windowList.size(); for ( int i = 0; i < n; i++ ) { final UIPositionedWindow w = windowList.get( i ); if ( w.isAssigned() ) { if ( w.isVisible() ) { w.getOverlay().update(); } } } } /** * This adds an overlay into the overlay manager system. This finds a free spot * in the list of overlays and inserts it, then pops it to the front. */ public UIPositionedWindow addOverlay( UIOverlayInterface overlay ) { UIPositionedWindow w = new UIPositionedWindow(); w.assign( overlay ); if ( overlay instanceof UIWindow ) ( (UIWindow)overlay ).setWindowManager( this ); if ( overlay instanceof UIWindow ) w.setDraggable( ( (UIWindow)overlay ).isDraggable() ); if ( overlay.isOpaque() ) { orderedWindows.addChild( w ); } else { windows.addChild( w ); } windowList.add( w ); return ( w ); } /** * This removes the overlay from the overlay system. The underlying * resources will be released. */ public void removeOverlay( UIOverlayInterface overlay ) { UIPositionedWindow w = getWindow( overlay ); if ( w != null ) { w.empty(); } } /** * Sets the visibility of the window */ public void setVisible( UIOverlayInterface overlay, boolean show ) { UIPositionedWindow w = getWindow( overlay ); if ( w != null ) { w.setVisible( show ); } } public boolean isVisible( UIOverlayInterface overlay ) { UIPositionedWindow w = getWindow( overlay ); if ( w == null ) return ( false ); return ( w.isVisible() ); } /** * This finds the current window associated with an overlay. */ private UIPositionedWindow getWindow( UIOverlayInterface overlay ) { UIPositionedWindow w = null; int n = windowList.size(); for ( int i = 0; i < n; i++ ) { w = windowList.get( i ); if ( w.getOverlay() == overlay ) { return ( w ); } } X3DLog.error( "Unknown overlay window referenced" ); return ( null ); } /** * Sets the position of the specified overlay */ public void setPosition( UIOverlayInterface overlay, int x, int y ) { UIPositionedWindow w = getWindow( overlay ); if ( w != null ) { w.setPosition( x, y, canvasDim ); /* Dimension d = new Dimension(); w.getOverlay().getSize(d); w.setPosition( (float)x, (float)canvasDim.height - y - d. height ); */ } } public int getX( UIOverlayInterface overlay ) { UIPositionedWindow w = getWindow( overlay ); if ( w != null ) return ( w.getX() ); return ( -1 ); } public int getY( UIOverlayInterface overlay ) { UIPositionedWindow w = getWindow( overlay ); if ( w != null ) return ( w.getY() ); return ( -1 ); } public void processEvent( AWTEvent e ) { if ( e instanceof MouseEvent ) { MouseEvent mev = (MouseEvent)e; processMouseEvent( new MouseEvent( (Component)mev.getSource(), mev.getID(), mev.getWhen(), mev.getModifiers(), mev.getX(), mev.getY(), mev.getClickCount(), false ) ); } else if ( e instanceof KeyEvent ) { if ( focus != null ) { Object o = focus.getOverlay(); if ( o instanceof UIWindow ) { ( (UIWindow)o ).dispatchEvent( e ); } } //else Log.print( "there is no focused window for key event " + e ); } } public void setOverlayFocus( UIOverlayInterface overlay ) { UIPositionedWindow w = getWindow( overlay ); setOverlayFocus( w ); } public void setOverlayFocus( UIPositionedWindow w ) { if ( w == focus ) return; boolean focusFalse = true; if ( focus != null ) { if ( focus.getOverlay() instanceof UIWindow ) { UIWindow uiw = (UIWindow)focus.getOverlay(); if ( uiw.last != null ) { uiw.last.dispatchEvent( new MouseEvent( uiw.last, MouseEvent.MOUSE_EXITED, 0, MouseEvent.BUTTON1_MASK, uiw.last.getLocation().x, uiw.last.getLocation().y, 0, false ) ); uiw.last = null; } if ( !( uiw.textComponentFocus ) || mouseClicked ) uiw.setFocus( false ); else focusFalse = false; } } if ( focusFalse ) { focus = w; if ( w != null ) { if ( focus.getOverlay() instanceof UIWindow ) ( (UIWindow)focus.getOverlay() ).setFocus( true ); } } } public synchronized void processMouseEvent( MouseEvent me ) { //boolean rightButton = ((MouseEvent.BUTTON3_MASK & me.getModifiers()) != 0); boolean leftButton = ( ( InputEvent.BUTTON1_MASK & me.getModifiers() ) != 0 ); UIPositionedWindow w = getWindow( me.getPoint() ); boolean eventConsumed = false; if ( me.getID() == MouseEvent.MOUSE_MOVED ) { if ( mode == OM_MOVE_WINDOW ) { focus.setPosition( me.getX(), me.getY(), canvasDim ); eventConsumed = true; } //else if (w == null) setOverlayFocus( (UIPositionedWindow)null ); } if ( me.getID() == MouseEvent.MOUSE_DRAGGED ) { //Log.log.println( LogType.EXHAUSTIVE, "Mouse dragged at " + me.getPoint() ); if ( mode == OM_NONE ) { mode = OM_DRAGGING_NONE; if ( w != null ) { Object o = w.getOverlay(); X3DLog.debug( "Overlay type is ", o.getClass().getSimpleName() ); if ( o instanceof UIDragDropInterface ) { me.translatePoint( -(int)w.getRectangle().getX(), -(int)w.getRectangle().getY() ); draggingInfo = ( (UIDragDropInterface)o ).startDrag( me ); if ( draggingInfo != null ) { X3DLog.debug( "Mouse dragging object!" ); // let a click go through for this since we are // dragging it anyway if ( o instanceof UIWindow ) { MouseEvent mec = new MouseEvent( me.getComponent(), MouseEvent.MOUSE_CLICKED, me.getWhen(), me.getModifiers(), me.getX(), me.getY(), me.getClickCount(), false ); ( (UIWindow)o ).dispatchEvent( mec ); } mode = OM_DRAGGING; // if the drag overlay has not been created yet go ahead // and make it if ( dragWindow == null ) { UIDraggingWindow dw = new UIDraggingWindow(); dragWindow = addOverlay( dw ); } // give the drag window the dragging information so it knows // how to draw itself ( (UIDraggingWindow)dragWindow.getOverlay() ).setDi( draggingInfo ); // translate the mouse event back to original screen position // and then set the dragging window position to the mouse position me.translatePoint( (int)w.getRectangle().getX(), (int)w.getRectangle().getY() ); dragWindow.setPosition( me.getX(), me.getY(), canvasDim ); // make the drag window visible dragWindow.setVisible( true ); eventConsumed = true; } else { // translate back me.translatePoint( (int)w.getRectangle().getX(), (int)w.getRectangle().getY() ); } } } } else if ( mode == OM_DRAGGING ) { dragWindow.setPosition( me.getX(), me.getY(), canvasDim ); // if there is an overlay underneath the icon, and it // is drag and drop enabled then check to see if this could // receive it. boolean canReceive = false; if ( w != null ) { Object o = w.getOverlay(); if ( o instanceof UIDragDropInterface ) { me.translatePoint( -(int)w.getRectangle().getX(), -(int)w.getRectangle().getY() ); canReceive = ( (UIDragDropInterface)o ).dragging( me, draggingInfo ); } } eventConsumed = true; ( (UIDraggingWindow)dragWindow.getOverlay() ).setCanDrop( canReceive ); } } if ( me.getID() == MouseEvent.MOUSE_RELEASED ) { if ( mode == OM_DRAGGING_NONE ) { X3DLog.debug( "Mouse done dragging none" ); mode = OM_NONE; } else if ( mode == OM_DRAGGING ) { mode = OM_NONE; dragWindow.setVisible( false ); boolean dropped = false; if ( w != null ) { X3DLog.debug( "Mouse drop on window" ); Object o = w.getOverlay(); if ( draggingInfo != null ) { if ( o instanceof UIDragDropInterface ) { // translate into coordinate space of window me.translatePoint( -(int)w.getRectangle().getX(), -(int)w.getRectangle().getY() ); dropped = ( (UIDragDropInterface)o ).dropped( me, draggingInfo ); // translate back me.translatePoint( +(int)w.getRectangle().getX(), +(int)w.getRectangle().getY() ); } } } // if dropped failed then callback if ( !dropped ) { if ( ( draggingInfo != null ) && ( draggingInfo.getFailure() != null ) ) { UIOverlayInterface o = null; if ( w != null ) o = w.getOverlay(); draggingInfo.getFailure().dropFailed( me.getX(), me.getY(), draggingInfo, o ); } } draggingInfo = null; eventConsumed = true; } } if ( me.getID() == MouseEvent.MOUSE_CLICKED ) { X3DLog.debug( "Mouse clicked at ", me.getPoint() ); if ( w != null ) setOverlayFocus( w ); // if we are moving the window then we don't care if there is a current window // under the cursor if ( ( mode == OM_MOVE_WINDOW ) && leftButton ) { mode = OM_NONE; X3DLog.debug( "Done moving window" ); focus.setPosition( me.getX(), me.getY(), canvasDim ); } else if ( w != null ) { X3DLog.debug( "Mouse clicked in window " ); // if the mouse is being left clicked in the upper left hand corner of // the window then go into the window movement mode. if ( w.isDraggable() ) { if ( leftButton && ( mode == OM_NONE ) ) { Rectangle bounds = new Rectangle( w.getRectangle() ); bounds.setSize( 15, 15 ); if ( bounds.contains( me.getPoint() ) ) { X3DLog.debug( "Begin moving window" ); if ( w != null ) setOverlayFocus( w ); mode = OM_MOVE_WINDOW; } } } } else { // if we get here then the click was not in a window, so we need to // do picking against the 3d scene to find the object, if there is one. setOverlayFocus( (UIPositionedWindow)null ); /* Log.print( "Attempting to find object at pick point" ); SynchronizedObject o = getPickedObject( me, mobiles ); if (o == null) { o = getPickedObject( me, structures ); } if (o != null) { Log.print( "Found picked object : " + o.getObjectName() ); CMM.post( new CMTarget( o ) ); } */ } } // if we get here, but we have not consumed the mouse message then // determine if the mouse event should be sent if ( !eventConsumed ) { if ( w != null ) { Object o = w.getOverlay(); if ( o instanceof UIWindow ) { if ( ( (UIWindow)o ).isDisabled() ) return; me.translatePoint( -(int)w.getRectangle().getX(), -(int)w.getRectangle().getY() ); ( (UIWindow)o ).dispatchEvent( me ); } } } } public UIWindowManager( Canvas3D c ) { super( new BaseRenderPassConfig() ); this.c3d = c; //check to see if we are using direct x or OpenGL /* Map m = c.queryProperties(); String nativeVersion = (String)m.get( "native.version" ); if (nativeVersion.startsWith( "DirectX" )) { isDirectX = true; } */ windowList = new ArrayList< UIPositionedWindow >( 20 ); BranchGroup root = this.getBranchGroup(); root.setPickable( true ); consoleTG = new TransformGroup(); root.addChild( consoleTG ); consoleTG.setTransform( new Transform3D() ); // define the dimensions and transforms used by the overlay manager canvasDim = new Dimension(); checkDim = new Dimension(); planeOffset = new Transform3D(); worldTransform = new Transform3D(); lastTransform = new Transform3D(); calcImagePlate(); /* Group p = XithEngine.getEngine().getUniverse().getPlatformGeometry(); Group pg = new Group(); pg.addChild(getTestShapeOpenGL( 0, 0, 64, 64, Color.red ) ); pg.addChild(getTestShapeOpenGL( 64, 0, 64, 64, Color.blue ) ); pg.addChild(getTestShapeOpenGL( 0, 64, 64, 64, Color.green ) ); pg.addChild(getTestShapeOpenGL( 64, 64, 64, 64, Color.magenta ) ); pg.addChild(getTestShapeOpenGL( 128, 128, 64, 64, Color.gray ) ); pg.addChild(getTestShapeOpenGL( 128 + 64, 128, 16, 64, Color.yellow ) ); p.addChild( pg ); */ //addTestShape( 0, 0, 100, 100); //addTestShape( 100, 0, 100, 100); // define the ordered group that will have all the UIPositionedWindow's. They // are placed in an ordered group so that we can control window stacking. windows = new Group(); windows.setIgnoreBounds( true ); consoleTG.addChild( windows ); orderedWindows = new OrderedGroup(); consoleTG.addChild( orderedWindows ); consoleTG.setTransform( planeOffset ); //c.setWindowManager( this ); } }