All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.checkerboard.alt.javafx.impls.FxCheckerboardViewerImpl Maven / Gradle / Ivy

There is a newer version: 3.3.9
Show newest version
package org.refcodes.checkerboard.alt.javafx.impls;

import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.refcodes.checkerboard.ChangePositionEvent;
import org.refcodes.checkerboard.Checkerboard;
import org.refcodes.checkerboard.CheckerboardEvent;
import org.refcodes.checkerboard.CheckerboardObserver;
import org.refcodes.checkerboard.DraggabilityChangedEvent;
import org.refcodes.checkerboard.GridDimensionChangedEvent;
import org.refcodes.checkerboard.GridModeChangedEvent;
import org.refcodes.checkerboard.Player;
import org.refcodes.checkerboard.PlayerAddedEvent;
import org.refcodes.checkerboard.PlayerEvent;
import org.refcodes.checkerboard.PlayerRemovedEvent;
import org.refcodes.checkerboard.PositionChangedEvent;
import org.refcodes.checkerboard.StateChangedEvent;
import org.refcodes.checkerboard.ViewportDimensionChangedEvent;
import org.refcodes.checkerboard.ViewportOffsetChangedEvent;
import org.refcodes.checkerboard.VisibilityChangedEvent;
import org.refcodes.checkerboard.alt.javafx.FxBackgroundFactory;
import org.refcodes.checkerboard.alt.javafx.FxCheckerboardViewer;
import org.refcodes.checkerboard.alt.javafx.FxSpriteFactory;
import org.refcodes.checkerboard.impls.AbstractGraphicalCheckerboardViewer;
import org.refcodes.component.ConfigureException;
import org.refcodes.component.InitializeException;
import org.refcodes.exception.ExceptionUtility;
import org.refcodes.exception.VetoException;
import org.refcodes.exception.VetoException.VetoRuntimeException;
import org.refcodes.graphical.FieldDimension;
import org.refcodes.graphical.GridDimension;
import org.refcodes.graphical.GridMode;
import org.refcodes.graphical.MoveMode;
import org.refcodes.graphical.Position;
import org.refcodes.graphical.ViewportDimension;
import org.refcodes.graphical.ViewportOffset;
import org.refcodes.graphical.ext.javafx.impls.FxViewportPaneImpl;
import org.refcodes.logger.RuntimeLogger;
import org.refcodes.logger.impls.RuntimeLoggerFactorySingleton;
import org.refcodes.mixin.Disposable;

import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

public class FxCheckerboardViewerImpl extends AbstractGraphicalCheckerboardViewer, FxBackgroundFactory, FxCheckerboardViewer> implements FxCheckerboardViewer, CheckerboardObserver {

	private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// CONSTANTS:
	// /////////////////////////////////////////////////////////////////////////

	private static final int TIMER_PERIOD_MS = 100;
	private static final int TIMER_DELAY_MS = 1000;
	private static final int RESIZE_FINISHED_DELAY_IN_MS = 150;

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private Group _checkers = new Group();
	private FxViewportPaneImpl _viewportPane = new FxViewportPaneImpl();
	private int _movePlayerDurationInMillis = 150;
	private int _adjustPlayerDurationInMillis = 50;
	private int _addPlayerDurationInMillis = 1000;
	private int _removePlayerDurationInMillis = 1000;
	private int _playerVisibilityDurationInMillis = 100;
	private int _changePlayerStateInMillis = 300;
	private int _resizeGridInMillis = 500;
	private int _initGridInMillis = 1500;
	private boolean _isVisible;
	private Node _backgroundNode = null;
	private Scene _scene = null;
	private Stage _stage = null;
	private Map, Node> _playerToSprite = new HashMap, Node>();
	private Map, DragPlayerEventHandler> _playerToDragEventHandler = new HashMap, DragPlayerEventHandler>();
	private FxCheckerboardViewerImpl.ResizeEventHandler _stageHandler;
	private long _lastResizeTimeInMs = -1;
	private Timer _viewTimer = null;
	private double _bordersH = Double.NaN;
	private double _bordersV = Double.NaN;
	private double _windowDecorationH = Double.NaN;
	private double _windowDecorationV = Double.NaN;

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	public FxCheckerboardViewerImpl( Checkerboard aCheckerboard ) {
		aCheckerboard.subscribeObserver( this );
	}

	// /////////////////////////////////////////////////////////////////////////
	// INJECTION:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public FxCheckerboardViewer withMovePlayerDurationInMillis( int aMovePlayerDurationInMillis ) {
		setMovePlayerDurationInMillis( aMovePlayerDurationInMillis );
		return this;
	}

	@Override
	public FxCheckerboardViewer withAddPlayerDurationInMillis( int aAddPlayerDurationInMillis ) {
		setAddPlayerDurationInMillis( aAddPlayerDurationInMillis );
		return this;
	}

	@Override
	public FxCheckerboardViewer withRemovePlayerDurationInMillis( int aRemovePlayerDurationInMillis ) {
		setRemovePlayerDurationInMillis( aRemovePlayerDurationInMillis );
		return this;
	}

	@Override
	public FxCheckerboardViewer withChangePlayerStateInMillis( int aChangePlayerStateInMillis ) {
		setChangePlayerStateInMillis( aChangePlayerStateInMillis );
		return this;
	}

	@Override
	public void setMovePlayerDurationInMillis( int aMovePlayerDurationInMillis ) {
		_movePlayerDurationInMillis = aMovePlayerDurationInMillis;
	}

	@Override
	public int getAddPlayerDurationInMillis() {
		return _addPlayerDurationInMillis;
	}

	@Override
	public void setAddPlayerDurationInMillis( int aAddPlayerDurationInMillis ) {
		_addPlayerDurationInMillis = aAddPlayerDurationInMillis;
	}

	@Override
	public int getRemovePlayerDurationInMillis() {
		return _removePlayerDurationInMillis;
	}

	@Override
	public void setRemovePlayerDurationInMillis( int aRemovePlayerDurationInMillis ) {
		_removePlayerDurationInMillis = aRemovePlayerDurationInMillis;
	}

	@Override
	public int getChangePlayerStateInMillis() {
		return _changePlayerStateInMillis;
	}

	@Override
	public void setChangePlayerStateInMillis( int changePlayerStateInMillis ) {
		_changePlayerStateInMillis = changePlayerStateInMillis;
	}

	@Override
	public int getMovePlayerDurationInMillis() {
		return _movePlayerDurationInMillis;
	}

	@Override
	public void setVisible( boolean isVisible ) {
		if ( _stage != null ) {
			if ( isVisible ) {
				if ( Platform.isFxApplicationThread() ) {
					_stage.show();
				}
				else {
					Platform.runLater( new Runnable() {
						@Override
						public void run() {
							_stage.show();
						}
					} );
				}
			}
			else {
				if ( Platform.isFxApplicationThread() ) {
					_stage.hide();
				}
				else {
					Platform.runLater( new Runnable() {

						@Override
						public void run() {
							_stage.hide();
						}

					} );
				}
			}
		}
		_isVisible = isVisible;
	}

	@Override
	public boolean isVisible() {
		return _isVisible;
	}

	@Override
	public int getViewportOffsetX() {
		return _viewportPane.getViewportOffsetX();
	}

	@Override
	public int getViewportOffsetY() {
		return _viewportPane.getViewportOffsetY();
	}

	// /////////////////////////////////////////////////////////////////////////
	// LIFECYCLE:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public synchronized void initialize() throws InitializeException {

		if ( _viewTimer == null ) {
			synchronized ( this ) {
				if ( _viewTimer == null ) {
					_viewTimer = new Timer( true );
					_viewTimer.schedule( new ViewDaemon(), TIMER_DELAY_MS, TIMER_PERIOD_MS );
				}
			}
		}

		if ( Platform.isFxApplicationThread() ) {
			runInitialize();
			if ( _stage != null ) {
				runInitialize( _stage );
			}
		}
		else {
			Platform.runLater( new Runnable() {
				@Override
				public void run() {
					try {
						runInitialize();
						if ( _stage != null ) {
							runInitialize( _stage );
						}
					}
					catch ( InitializeException e ) {
						LOGGER.error( "Exception during initialization: " + ExceptionUtility.toMessage( e ), e );
					}
				}

			} );
		}
	}

	@Override
	public void initialize( Stage aContext ) throws ConfigureException {
		if ( Platform.isFxApplicationThread() ) {
			try {
				initialize();
			}
			catch ( InitializeException e ) {
				throw new ConfigureException( "Exception during initialization: " + ExceptionUtility.toMessage( e ), e );
			}
			runInitialize( aContext );
		}
		else {
			Platform.runLater( new Runnable() {
				@Override
				public void run() {
					try {
						runInitialize();
						runInitialize( aContext );
					}
					catch ( InitializeException e ) {
						LOGGER.error( "Exception during initialization: " + ExceptionUtility.toMessage( e ), e );
					}
				}
			} );
		}
	}

	@Override
	public FxCheckerboardViewer withInitialize( Stage aContext ) throws ConfigureException {
		initialize( aContext );
		return this;
	}

	@Override
	public void destroy() {
		LOGGER.debug( "Destroying ..." );
		if ( _viewTimer != null ) {
			synchronized ( this ) {
				if ( _viewTimer != null ) {
					_viewTimer.cancel();
					_viewTimer.purge();
					_viewTimer = null;
				}
			}
		}
	}

	private void runInitialize() throws InitializeException {
		try {
			LOGGER.debug( "Initializing ..." );
			if ( getViewportWidth() == -1 ) {
				setViewportWidth( getGridWidth() );
			}
			if ( getViewportHeight() == -1 ) {
				setViewportHeight( getGridHeight() );
			}
			if ( getViewportWidth() == -1 || getViewportHeight() == -1 ) { throw new IllegalStateException( "The viewport dimension for the checkerboard must be set!" ); }
			fxResizeViewport( this, null, this, _initGridInMillis );
			fxResizeGrid( this, null, _initGridInMillis );
			switch ( getScaleMode() ) {
			case SCALE_GRID:
			case SCALE_FIELDS: {
				_viewportPane.setContent( _checkers );
				_viewportPane.setViewportOffset( this );
				_viewportPane.setFieldDimension( this );
				_scene = new Scene( _viewportPane, Color.BLACK );
				break;
			}
			case NONE: {
				StackPane thePane = new StackPane();
				thePane.getChildren().add( _checkers );
				StackPane.setAlignment( _checkers, Pos.CENTER );
				_scene = new Scene( thePane, Color.BLACK );
				break;
			}
			}
		}
		catch ( Exception e ) {
			throw new InitializeException( "Exception during initialization: " + ExceptionUtility.toMessage( e ), e );
		}
	}

	private void runInitialize( Stage aContext ) throws ConfigureException {
		try {
			if ( _stage != aContext ) {
				if ( _stageHandler != null ) {
					_stageHandler.dispose();
				}
				_stage = aContext;
				_stageHandler = new ResizeEventHandler( aContext );
			}
			fxResizeStage();
		}
		catch ( Exception e ) {
			throw new ConfigureException( aContext, "Exception during initialization: " + ExceptionUtility.toMessage( e ), e );
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public void onPlayerEvent( PlayerEvent aEvent, Checkerboard aCheckerboard ) {}

	@Override
	public void onCheckerboardEvent( CheckerboardEvent aEvent ) {}

	@Override
	public void onChangePositionEvent( ChangePositionEvent aEvent, Checkerboard aCheckerboard ) throws VetoException {
		LOGGER.debug( aEvent.toString() );
	}

	@Override
	public void onPositionChangedEvent( PositionChangedEvent aEvent, Checkerboard aCheckerboard ) {
		LOGGER.debug( aEvent.toString() );
		fxMovePlayer( aEvent.getSource(), aEvent.getPrecedingPosition(), _movePlayerDurationInMillis );
	}

	@Override
	public void onVisibilityChangedEvent( VisibilityChangedEvent aEvent, Checkerboard aCheckerboard ) {
		LOGGER.debug( aEvent.toString() );
		fxPlayerVisibility( aEvent.getSource(), _playerVisibilityDurationInMillis );
	}

	@Override
	public void onDraggabilityChangedEvent( DraggabilityChangedEvent aEvent, Checkerboard aCheckerboard ) {
		LOGGER.debug( aEvent.toString() );
		fxPlayerDraggability( aEvent.getSource() );
	}

	@Override
	public void onStateChangedEvent( StateChangedEvent aEvent, Checkerboard aCheckerboard ) {
		LOGGER.debug( aEvent.toString() );
		fxRemovePlayer( aEvent.getSource(), _changePlayerStateInMillis / 2 );
		fxAddPlayer( aEvent.getSource(), _changePlayerStateInMillis / 2 );
	}

	@Override
	public void onPlayerAddedEvent( PlayerAddedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		fxAddPlayer( aEvent.getPlayer(), _addPlayerDurationInMillis );
	}

	@Override
	public void onPlayerRemovedEvent( PlayerRemovedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		fxRemovePlayer( aEvent.getPlayer(), _removePlayerDurationInMillis );
	}

	@Override
	public void onGridModeChangedEvent( GridModeChangedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		_viewportPane.setGridMode( aEvent.getGridMode() );
		initMinStageWidth();
		initMinStageHeight();
	}

	@Override
	public void onGridDimensionChangedEvent( GridDimensionChangedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		fxResizeGrid( aEvent, aEvent.getPrecedingGridDimension(), _resizeGridInMillis );
	}

	@Override
	public void onViewportOffsetChangedEvent( ViewportOffsetChangedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		_viewportPane.setViewportOffset( this );
	}

	@Override
	public void onViewportDimensionChangedEvent( ViewportDimensionChangedEvent aEvent ) {
		LOGGER.debug( aEvent.toString() );
		fxResizeViewport( aEvent, aEvent.getPrecedingViewportDimension(), aEvent.getViewportOffset(), _resizeGridInMillis );
	}

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	protected void resetPlayers( int aDurationInMillis ) {
		for ( Player ePlayer : getCheckerboard().getPlayers() ) {
			fxResetPlayer( ePlayer, aDurationInMillis );
		}
	}

	protected void scalePlayers( FieldDimension aFieldDimension, FieldDimension aPrecedingFieldDimension ) {
		for ( Player ePlayer : getCheckerboard().getPlayers() ) {
			fxScalePlayer( ePlayer, aFieldDimension, aPrecedingFieldDimension );
		}
	}

	protected void hidePlayers( int aDurationInMillis ) {
		for ( Player ePlayer : getCheckerboard().getPlayers() ) {
			fxHidePlayer( ePlayer, aDurationInMillis );
		}
	}

	private int toPixelPositionX( Position aPosition ) {
		return (getFieldWidth() + getFieldGap()) * aPosition.getPositionX() + (getGridMode() == GridMode.CLOSED ? getFieldGap() : 0);
	}

	private int toPixelPositionY( Position aPosition ) {
		return (getFieldHeight() + getFieldGap()) * aPosition.getPositionY() + (getGridMode() == GridMode.CLOSED ? getFieldGap() : 0);
	}

	// @formatter:off
	/*
		private int toPixelOffsetX() {
			return (getFieldWidth() + getFieldGap()) * getViewportOffsetX();
		}
	*/
	// @formatter:on

	// @formatter:off
	/*
		private int toPixelOffsetY() {
			return (getFieldHeight() + getFieldGap()) * getViewportOffsetY();
		}
	*/
	// @formatter:on

	private boolean isViewportPosX( int aPositionX ) {
		return (aPositionX >= getViewportOffsetX() && aPositionX < getViewportWidth() + getViewportOffsetX());
	}

	private boolean isViewportPosY( int aPositionY ) {
		return (aPositionY >= getViewportOffsetY() && aPositionY < getViewportHeight() + getViewportOffsetY());
	}

	// @formatter:off
	/*
		private boolean isViewportPosition( int aPositionX, int aPositionY ) {
			return isViewportPosX( aPositionX ) && isViewportPosY( aPositionY );
		}
	*/
	// @formatter:on

	private boolean isViewport( Position aPosition ) {
		return isViewportPosX( aPosition.getPositionX() ) && isViewportPosY( aPosition.getPositionY() );
	}

	private void initBordersH( double aWindowWidth ) {
		if ( Double.isNaN( _bordersH ) ) {
			double theBoardersH = aWindowWidth - ((getViewportWidth() - 1) * getFieldGap());
			if ( getCheckerboard().getGridMode() == GridMode.CLOSED ) theBoardersH -= getFieldGap() * 2;
			theBoardersH = theBoardersH - (getViewportWidth() * getFieldWidth());
			// -------------------------------------------------------------
			// Adjust one (phantom) gap for accurate calculation:
			// -------------------------------------------------------------
			if ( getCheckerboard().getGridMode() == GridMode.CLOSED ) theBoardersH += getFieldGap();
			else theBoardersH -= getFieldGap();
			// -------------------------------------------------------------
			LOGGER.debug( "Horizontal (left and right) [phantom] borders from width <" + aWindowWidth + "> := " + theBoardersH );
			_windowDecorationH = _stage.getWidth() - _scene.getWidth();
			_bordersH = theBoardersH;
		}
	}

	private void initBordersV( double aWindowHeight ) {
		if ( Double.isNaN( _bordersV ) ) {
			double theBoardersV = aWindowHeight - ((getViewportHeight() - 1) * getFieldGap());
			if ( getCheckerboard().getGridMode() == GridMode.CLOSED ) theBoardersV -= getFieldGap() * 2;
			theBoardersV = theBoardersV - (getViewportHeight() * getFieldHeight());
			// -------------------------------------------------------------
			// Adjust one (phantom) gap for accurate calculation:
			// -------------------------------------------------------------
			if ( getCheckerboard().getGridMode() == GridMode.CLOSED ) theBoardersV += getFieldGap();
			else theBoardersV -= getFieldGap();
			// -------------------------------------------------------------
			LOGGER.debug( "Vertical (top and bottom) [phantom] borders from height <" + aWindowHeight + "> := " + theBoardersV );
			_windowDecorationV = _stage.getHeight() - _scene.getHeight();
			_bordersV = theBoardersV;
		}
	}

	private void initMinStageWidth() {
		if ( _stage != null && _scene != null ) {
			if ( Double.isNaN( _bordersH ) ) {
				initBordersH( _scene.getWidth() );
			}
			if ( !Double.isNaN( _bordersH ) ) {
				int theWidth = (int) _bordersH;
				theWidth += (getViewportWidth() * getFieldWidth() + getViewportWidth() * getFieldGap());
				_stage.setMinWidth( theWidth + _windowDecorationH );
			}
		}
	}

	private void initMinStageHeight() {
		if ( _stage != null && _scene != null ) {
			if ( Double.isNaN( _bordersV ) ) {
				initBordersV( _scene.getHeight() );
			}
			if ( !Double.isNaN( _bordersV ) ) {
				int theHeight = (int) _bordersV;
				theHeight += (getViewportHeight() * getFieldHeight() + getViewportHeight() * getFieldGap());
				_stage.setMinHeight( theHeight + _windowDecorationV );
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// FX-THREAD MANAGEMENT:
	// /////////////////////////////////////////////////////////////////////////

	private void fxMovePlayer( Player aPlayer, Position aPrecedingPosition, int aDurationInMillis ) {
		Node theSprite;
		synchronized ( this ) {
			theSprite = _playerToSprite.get( aPlayer );
		}
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( _checkers.getChildren().remove( theSprite ) ) {
					_checkers.getChildren().add( theSprite );
					if ( getMoveMode() == MoveMode.SMOOTH ) {
						TranslateTransition theTransition = new TranslateTransition( Duration.millis( aDurationInMillis ), theSprite );
						theTransition.setByX( toPixelPositionX( aPlayer ) - theSprite.getTranslateX() );
						theTransition.setByY( toPixelPositionY( aPlayer ) - theSprite.getTranslateY() );
						theTransition.setCycleCount( 1 );
						theTransition.setAutoReverse( false );
						theTransition.play();
					}
					else {
						theSprite.setTranslateX( toPixelPositionX( aPlayer ) );
						theSprite.setTranslateY( toPixelPositionY( aPlayer ) );
					}
				}
			}
		};
		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}

	private void fxScalePlayer( Player aPlayer, FieldDimension aFieldDimension, FieldDimension aPrecedingFieldDimension ) {
		Node theSprite;
		synchronized ( _playerToSprite ) {
			theSprite = _playerToSprite.get( aPlayer );
		}
		theSprite.setScaleX( aFieldDimension.getFieldWidth() / theSprite.getBoundsInLocal().getWidth() );
		theSprite.setScaleY( aFieldDimension.getFieldHeight() / theSprite.getBoundsInLocal().getHeight() );
		double theLayoutX = (aFieldDimension.getFieldWidth() - theSprite.getBoundsInLocal().getWidth()) / 2;
		double theLayoutY = (aFieldDimension.getFieldHeight() - theSprite.getBoundsInLocal().getHeight()) / 2;
		theSprite.setLayoutX( theLayoutX );
		theSprite.setLayoutY( theLayoutY );
		theSprite.setTranslateX( toPixelPositionX( aPlayer ) );
		theSprite.setTranslateY( toPixelPositionY( aPlayer ) );
	}

	private void fxResetPlayer( Player aPlayer, int aDurationInMillis ) {
		Node theSprite;
		Node thePrevSprite;
		synchronized ( _playerToSprite ) {
			theSprite = getSpriteFactory().createInstance( aPlayer.getState(), this );
			thePrevSprite = _playerToSprite.put( aPlayer, theSprite );
		}
		if ( aPlayer.isDraggable() ) {
			DragPlayerEventHandler theDragEventHandler = new DragPlayerEventHandler( aPlayer, theSprite );
			_playerToDragEventHandler.put( aPlayer, theDragEventHandler );
		}
		theSprite.setOpacity( 0 );
		Point2D thePoint = theSprite.localToParent( 0, 0 );
		theSprite.setLayoutX( -(thePoint.getX()) );
		theSprite.setLayoutY( -(thePoint.getY()) );
		theSprite.setTranslateX( toPixelPositionX( aPlayer ) );
		theSprite.setTranslateY( toPixelPositionY( aPlayer ) );
		_checkers.getChildren().add( theSprite );
		if ( thePrevSprite != null ) _checkers.getChildren().remove( thePrevSprite );
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( aPlayer.isVisible() && theSprite.getOpacity() == 0 ) {
					FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
					theTransition.setFromValue( 0 );
					theTransition.setToValue( 1 );
					theTransition.setCycleCount( 1 );
					theTransition.setAutoReverse( false );
					theTransition.play();
				}
			}
		};
		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}

	private void fxHidePlayer( Player aPlayer, int aDurationInMillis ) {
		Node theSprite;
		synchronized ( _playerToSprite ) {
			theSprite = _playerToSprite.get( aPlayer );
		}
		if ( theSprite != null ) {
			if ( aDurationInMillis == 0 ) {
				theSprite.setOpacity( 0 );
			}
			else if ( theSprite.getOpacity() != 0 ) {
				Runnable theRunner = new Runnable() {
					@Override
					public void run() {
						FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
						theTransition.setFromValue( theSprite.getOpacity() );
						theTransition.setToValue( 0 );
						theTransition.setCycleCount( 1 );
						theTransition.setAutoReverse( false );
						theTransition.play();
					}
				};
				if ( Platform.isFxApplicationThread() ) {
					theRunner.run();
				}
				else {
					Platform.runLater( theRunner );
				}
			}
		}
	}

	private void fxAddPlayer( Player aPlayer, int aDurationInMillis ) {
		Node theSprite = getSpriteFactory().createInstance( aPlayer.getState(), this );
		synchronized ( _playerToSprite ) {
			_playerToSprite.put( aPlayer, theSprite );
		}
		if ( aPlayer.isDraggable() ) {
			DragPlayerEventHandler theDragEventHandler = new DragPlayerEventHandler( aPlayer, theSprite );
			_playerToDragEventHandler.put( aPlayer, theDragEventHandler );
		}
		Point2D thePoint = theSprite.localToParent( 0, 0 );
		theSprite.setLayoutX( -(thePoint.getX()) );
		theSprite.setLayoutY( -(thePoint.getY()) );
		theSprite.setTranslateX( toPixelPositionX( aPlayer ) );
		theSprite.setTranslateY( toPixelPositionY( aPlayer ) );

		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( isViewport( aPlayer ) ) {
					theSprite.setOpacity( 0 );
					_checkers.getChildren().add( theSprite );
					if ( aPlayer.isVisible() ) {
						FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
						theTransition.setFromValue( 0 );
						theTransition.setToValue( 1 );
						theTransition.setCycleCount( 1 );
						theTransition.setAutoReverse( false );
						theTransition.play();
					}
				}
				else {
					theSprite.setVisible( false );
					_checkers.getChildren().add( theSprite );
				}
			}
		};
		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}

	}

	private void fxRemovePlayer( Player aPlayer, int aDurationInMillis ) {
		Node theSprite;
		synchronized ( _playerToSprite ) {
			theSprite = _playerToSprite.remove( aPlayer );
		}
		DragPlayerEventHandler theDragEventHandler = _playerToDragEventHandler.remove( aPlayer );
		if ( theDragEventHandler != null ) {
			theDragEventHandler.dispose();
		}
		if ( theSprite != null ) {
			FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
			theTransition.setFromValue( 1 );
			theTransition.setToValue( 0 );
			theTransition.setCycleCount( 1 );
			theTransition.setAutoReverse( false );
			theTransition.setOnFinished( new EventHandler() {
				@Override
				public void handle( ActionEvent event ) {
					_checkers.getChildren().remove( theSprite );
				}
			} );
			theTransition.play();
		}
	}

	private void fxPlayerDraggability( Player aPlayer ) {
		Node theSprite;
		synchronized ( _playerToSprite ) {
			theSprite = _playerToSprite.get( aPlayer );
		}
		if ( theSprite != null ) {
			if ( aPlayer.isDraggable() && !_playerToDragEventHandler.containsKey( aPlayer ) ) {
				DragPlayerEventHandler theDragEventHandler = new DragPlayerEventHandler( aPlayer, theSprite );
				_playerToDragEventHandler.put( aPlayer, theDragEventHandler );
			}
			else {
				DragPlayerEventHandler theDragEventHandler = _playerToDragEventHandler.remove( aPlayer );
				if ( theDragEventHandler != null ) {
					theDragEventHandler.dispose();
				}
			}
		}
		else {
			throw new IllegalStateException( "The player <" + aPlayer + "> is unknwon by this checkerboard." );
		}
	}

	// @formatter:off
	/*
		private void fxPlayerVisibility( Player aPlayer, boolean isVisible, int aDurationInMillis ) {
			Node theSprite;
			synchronized ( _playerToSprite ) {
				theSprite = _playerToSprite.get( aPlayer );
			}
			if ( theSprite != null ) {
				FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
				theTransition.setFromValue( isVisible ? 0 : 1 );
				theTransition.setToValue( isVisible ? 1 : 0 );
				theTransition.setCycleCount( 1 );
				theTransition.setAutoReverse( false );
				if ( !isVisible ) {
					theTransition.setOnFinished( new EventHandler() {
						@Override
						public void handle( ActionEvent event ) {
							theSprite.setVisible( false );
						}
					} );
				}
				if ( isVisible ) {
					if ( !theSprite.visibleProperty().getValue() ) theSprite.setVisible( true );
				}
				theTransition.play();
			}
		}
	*/
	// @formatter:on

	private void fxPlayerVisibility( Player aPlayer, int aDurationInMillis ) {
		Node theSprite;
		synchronized ( _playerToSprite ) {
			theSprite = _playerToSprite.get( aPlayer );
		}
		if ( theSprite != null ) {
			FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ), theSprite );
			theTransition.setFromValue( aPlayer.isVisible() ? 0 : 1 );
			theTransition.setToValue( aPlayer.isVisible() ? 1 : 0 );
			theTransition.setCycleCount( 1 );
			theTransition.setAutoReverse( false );
			theTransition.play();
		}
	}

	// @formatter:off
	/*
	private synchronized void fxResizeFields( FieldDimensionChangedEvent> aEvent, FieldDimension precedingFieldDimension ) {
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( getBackground() != null ) {
					int index = 0;
					Node theOldBackgroundNode = _backgroundNode;
					_backgroundNode = getBackground().createInstance( FxCheckerboardViewerImpl.this );
					if ( theOldBackgroundNode != null ) {
						index = _checkers.getChildren().indexOf( theOldBackgroundNode );
						_checkers.getChildren().remove( _checkers.getChildren().remove( theOldBackgroundNode ) );
					}
					_checkers.getChildren().add( index, _backgroundNode );
				}
				fxResizeStage();
			}
		};
		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}
	*/
	// @formatter:on

	private synchronized void fxResizeViewport( ViewportDimension aDimension, ViewportDimension aPrecedingDimension, ViewportOffset aOffset, int aDurationInMillis ) {
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				_viewportPane.setFieldDimension( FxCheckerboardViewerImpl.this );
				_viewportPane.setViewportDimension( aDimension );
				fxResizeStage();
			}
		};

		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}

	private synchronized void fxResizeGrid( GridDimension aDimension, GridDimension aPrecedingDimension, int aDurationInMillis ) {
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( getBackgroundFactory() != null ) {
					fxUpdateBackground( aDimension, aPrecedingDimension, aDurationInMillis );
				}
				fxResizeStage();
			}
		};

		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}

	private void fxResizeStage() {
		Runnable theRunner = new Runnable() {
			@Override
			public void run() {
				if ( _stage != null ) {
					switch ( getScaleMode() ) {
					case SCALE_GRID:
					case SCALE_FIELDS: {
						if ( _stage != null ) {
							_stage.setScene( _scene );
						}
					}
						break;
					case NONE: {
						if ( _stage != null ) {
							_stage.setScene( _scene );
							_stage.sizeToScene();
							_stage.setResizable( false );
						}
						break;
					}
					}
				}
			}
		};
		if ( Platform.isFxApplicationThread() ) {
			theRunner.run();
		}
		else {
			Platform.runLater( theRunner );
		}
	}

	private void fxUpdateBackground( GridDimension aDimension, GridDimension aPrecedingDimension, int aDurationInMillis ) {
		int index = 0;
		Node theOldBackgroundNode = _backgroundNode;
		Node theNewBackgroundNode = getBackgroundFactory().createInstance( FxCheckerboardViewerImpl.this );
		FadeTransition theTransition = new FadeTransition( Duration.millis( aDurationInMillis ) );
		if ( theOldBackgroundNode != null ) {
			index = _checkers.getChildren().indexOf( theOldBackgroundNode );
			theTransition.setOnFinished( new EventHandler() {
				@Override
				public void handle( ActionEvent event ) {
					_checkers.getChildren().remove( _checkers.getChildren().remove( theOldBackgroundNode ) );
					fxResizeStage();
				}
			} );
		}
		Node theTransitionNode = null;
		double theFromValue = 0;
		double theToValue = 1;
		if ( aPrecedingDimension != null && aDimension.getGridWidth() >= aPrecedingDimension.getGridWidth() && aDimension.getGridHeight() >= aPrecedingDimension.getGridHeight() ) {
			theTransitionNode = theNewBackgroundNode;
			theNewBackgroundNode.setOpacity( 0 );
			theFromValue = 0;
			theToValue = 1;
			index++;
		}
		else if ( aPrecedingDimension != null && aDimension.getGridWidth() <= aPrecedingDimension.getGridWidth() && aDimension.getGridHeight() <= aPrecedingDimension.getGridHeight() ) {
			theTransitionNode = theOldBackgroundNode;
			theFromValue = 1;
			theToValue = 0;
		}

		if ( aPrecedingDimension == null ) {
			theTransitionNode = theNewBackgroundNode;
			theNewBackgroundNode.setOpacity( 0 );
		}

		_backgroundNode = theNewBackgroundNode;
		_checkers.getChildren().add( index, _backgroundNode );
		theTransition.setNode( theTransitionNode );
		theTransition.setFromValue( theFromValue );
		theTransition.setToValue( theToValue );
		theTransition.setCycleCount( 1 );
		theTransition.setAutoReverse( false );
		theTransition.play();
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// VIEW DAEMON:
	// /////////////////////////////////////////////////////////////////////////

	private class ViewDaemon extends TimerTask {
		@Override
		public void run() {
			if ( _stage != null && _lastResizeTimeInMs != -1 && _lastResizeTimeInMs + RESIZE_FINISHED_DELAY_IN_MS < System.currentTimeMillis() ) {
				_lastResizeTimeInMs = -1;
				Runnable theRunner = new Runnable() {
					@Override
					public void run() {
						if ( Double.isNaN( _bordersH ) ) {
							initMinStageWidth();
						}
						if ( Double.isNaN( _bordersV ) ) {
							initMinStageHeight();
						}
					}
				};
				if ( Platform.isFxApplicationThread() ) {
					theRunner.run();
				}
				else {
					Platform.runLater( theRunner );
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// DRAG PLAYER EVENT HANDLER:
	// /////////////////////////////////////////////////////////////////////////

	private class DragPlayerEventHandler implements Disposable {

		private double _sceneX;
		private double _sceneY;
		private double _translateX;
		private double _translateY;
		private Player _player;
		private Node _sprite;

		public DragPlayerEventHandler( Player aPlayer, Node aSprite ) {
			aSprite.setOnMousePressed( _onMousePressedEventHandler );
			aSprite.setOnMouseDragged( _onMouseDraggedEventHandler );
			aSprite.setOnMouseReleased( _onMouseReleasedEventHandler );
			_player = aPlayer;
			_sprite = aSprite;
		}

		private EventHandler _onMousePressedEventHandler = new EventHandler() {
			@Override
			public void handle( MouseEvent aEvent ) {
				if ( _player.isVisible() ) {
					Node theSprite = (Node) (aEvent.getSource());
					// _checkerboard.getChildren().remove( theSprite );
					// _checkerboard.getChildren().add( theSprite );
					theSprite.setOpacity( 0.5 );
					_sceneX = aEvent.getSceneX();
					_sceneY = aEvent.getSceneY();
					_translateX = theSprite.getTranslateX();
					_translateY = theSprite.getTranslateY();
					LOGGER.debug( "Player mouse press X := " + aEvent.getSceneX() );
					LOGGER.debug( "Player mouse press Y := " + aEvent.getSceneY() );
					aEvent.consume();
				}
			}
		};

		private EventHandler _onMouseDraggedEventHandler = new EventHandler() {
			@Override
			public void handle( MouseEvent aEvent ) {
				if ( _player.isVisible() ) {
					Node theSprite = (Node) (aEvent.getSource());
					double theSceneOffsetX = aEvent.getSceneX() - _sceneX;
					double theSceneOffsetY = aEvent.getSceneY() - _sceneY;
					double theTranslateX = _translateX + theSceneOffsetX;
					double theTranslateY = _translateY + theSceneOffsetY;
					// if ( theTranslateX < -(_bordersH / 2) ) return;
					// if ( theTranslateY < -(_bordersV / 2) ) return;
					int theBorderWidth = (int) _bordersH / 2;
					int theBorderHeight = (int) _bordersV / 2;

					// @formatter:off
					/*
						 LOGGER.debug( "Translate X := " + theTranslateX );
						 LOGGER.debug( "Translate Y := " + theTranslateY );
						 LOGGER.debug( "Field width = " + getFieldWidth() );
						 LOGGER.debug( "Field height = " + getFieldHeight() );
						 LOGGER.debug( "Field gap = " + getFieldGap() );
						 LOGGER.debug( "Viewport offset X = " + getViewportOffsetX() );
						 LOGGER.debug( "Viewport offset Y = " + getViewportOffsetY() );
						 LOGGER.debug( "Grid width = " + getGridWidth() );
						 LOGGER.debug( "Grid height = " + getGridHeight() );
						 LOGGER.debug( "Border width := " + theBorderWidth );
						 LOGGER.debug( "Border height := " + theBorderHeight );
						 LOGGER.debug( "Absolute Field width := " + (getFieldWidth() + getFieldGap()) );
						 LOGGER.debug( "Absolute Field height := " + (getFieldHeight() + getFieldGap()) );
						 LOGGER.debug( "Offset X + grid with - 1 := " + (getViewportOffsetX() + getGridWidth() - 1) );
						 LOGGER.debug( "Offset Y + grid height - 1 := " + (getViewportOffsetY() + getGridHeight() - 1) );
						 LOGGER.debug( "... for X := " + ( getFieldWidth() + getFieldGap()) * (getViewportOffsetX() + getGridWidth() - 1) );
						 LOGGER.debug( "... for Y := " + ( getFieldHeight() + getFieldGap()) * (getViewportOffsetY() + getGridHeight() - 1) );
					*/
					// @formatter:on

					if ( theTranslateX >= -theBorderWidth && theTranslateX <= (getFieldWidth() + getFieldGap()) * (getGridWidth() - 1) + theBorderWidth ) theSprite.setTranslateX( theTranslateX );
					if ( theTranslateY >= -theBorderHeight && theTranslateY <= (getFieldHeight() + getFieldGap()) * (getGridHeight() - 1) + theBorderHeight ) theSprite.setTranslateY( theTranslateY );

					aEvent.consume();
				}
			}
		};

		private EventHandler _onMouseReleasedEventHandler = new EventHandler() {
			@Override
			public void handle( MouseEvent aEvent ) {
				Node theSprite = (Node) (aEvent.getSource());
				if ( _player.isVisible() ) {
					double theSceneOffsetX = aEvent.getSceneX() - _sceneX;
					double theSceneOffsetY = aEvent.getSceneY() - _sceneY;
					double theStepX = Math.round( theSceneOffsetX / (getFieldWidth() + getFieldGap()) );
					double theStepY = Math.round( theSceneOffsetY / (getFieldHeight() + getFieldGap()) );
					int thePosX = (int) (_player.getPositionX() + theStepX);
					int thePosY = (int) (_player.getPositionY() + theStepY);
					if ( thePosX >= getGridWidth() || thePosX < 0 || thePosY >= getGridHeight() || thePosY < 0 ) {
						theStepY = 0;
						theStepX = 0;
						thePosY = (int) (_player.getPositionY() + theStepY);
						thePosX = (int) (_player.getPositionX() + theStepX);
					}
					try {
						_player.setPosition( thePosX, thePosY );
					}
					catch ( VetoRuntimeException e ) {
						LOGGER.warn( "Change player <" + _player + "> position to (" + thePosX + ", " + thePosY + ") has been vetoed: " + ExceptionUtility.toMessage( e ), e );
						theStepX = 0;
						theStepY = 0;
					}
					if ( theStepX == 0 && theStepY == 0 ) fxMovePlayer( _player, _player, _adjustPlayerDurationInMillis );
					theSprite.setOpacity( 1 );
					// @formatter:off
					/*
						LOGGER.debug( "Player mouse release X := " + aEvent.getSceneX() );
						LOGGER.debug( "Player mouse release Y := " + aEvent.getSceneY() );
						LOGGER.debug( "Player mouse release offset X := " + theSceneOffsetX );
						LOGGER.debug( "Player mouse release offset Y := " + theSceneOffsetY );
						LOGGER.debug( "Player grid step X := " + theStepX );
						LOGGER.debug( "Player grid step Y := " + theStepY );
						LOGGER.debug( "Player new pos X := " + thePosX );
						LOGGER.debug( "Player new pos Y := " + thePosY );
					*/
					// @formatter:on
					aEvent.consume();
				}
				// else {
				// theSprite.setTranslateX( toPositionX( _player ) );
				// theSprite.setTranslateY( toPositionY( _player ) );
				// }
			}
		};

		@Override
		public void dispose() {
			_sprite.setOnMousePressed( null );
			_sprite.setOnMouseDragged( null );
			_sprite.setOnMouseReleased( null );
			_sprite = null;
			_player = null;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// RESIZE EVENT HANDLER:
	// /////////////////////////////////////////////////////////////////////////
	private class ResizeEventHandler implements Disposable {

		private Stage _window;

		public ResizeEventHandler( Stage aWindow ) {
			aWindow.widthProperty().addListener( _onWindowWidthChangedEventHandler );
			aWindow.heightProperty().addListener( _onWindowHeightChangedEventHandler );
			_window = aWindow;
		}

		// ---------------------------------------------------------------------
		// WINDOW WIDTH CHANGE HANDLER:
		// ---------------------------------------------------------------------
		private ChangeListener _onWindowWidthChangedEventHandler = new ChangeListener() {

			@Override
			public void changed( ObservableValue observable, Number aOldValue, Number aNewValue ) {
				_lastResizeTimeInMs = System.currentTimeMillis();
				if ( Math.abs( aNewValue.doubleValue() - aOldValue.doubleValue() ) > 10 ) return;

				// -------------------------------------------------------------
				// Calculate the sum of the H-borders:
				// -------------------------------------------------------------
				if ( Double.isNaN( _bordersH ) && !Double.isNaN( aOldValue.doubleValue() ) ) {
					initBordersH( aOldValue.doubleValue() );
					initMinStageWidth();
				}
				// -------------------------------------------------------------

				switch ( getScaleMode() ) {
				case SCALE_GRID:
					int theNewViewportWidth = toScaledViewportDimension( aNewValue.doubleValue(), getViewportWidth(), getFieldWidth(), getFieldGap(), _bordersH + _windowDecorationH );
					if ( theNewViewportWidth != -1 ) {
						LOGGER.debug( "Viewport width changed to := " + theNewViewportWidth );
						setViewportWidth( theNewViewportWidth );
					}
					break;
				case SCALE_FIELDS:
					int theNewFieldWidth = toScaledFieldDimension( aNewValue.doubleValue(), getViewportWidth(), getFieldWidth(), getFieldGap(), _bordersH + _windowDecorationH );
					if ( theNewFieldWidth != -1 ) {
						LOGGER.debug( "Field width changed to := " + theNewFieldWidth );
						setFieldWidth( theNewFieldWidth );
					}
					break;
				case NONE:
					break;
				}
			}
		};

		// ---------------------------------------------------------------------
		// WINDOW HEIGHT CHANGE HANDLER:
		// ---------------------------------------------------------------------
		private ChangeListener _onWindowHeightChangedEventHandler = new ChangeListener() {

			@Override
			public void changed( ObservableValue observable, Number aOldValue, Number aNewValue ) {
				_lastResizeTimeInMs = System.currentTimeMillis();
				if ( Math.abs( aNewValue.doubleValue() - aOldValue.doubleValue() ) > 10 ) return;

				// -------------------------------------------------------------
				// Calculate the sum of the H-borders:
				// -------------------------------------------------------------
				if ( Double.isNaN( _bordersV ) && !Double.isNaN( aOldValue.doubleValue() ) ) {
					initBordersV( aOldValue.doubleValue() );
					initMinStageHeight();
				}
				// -------------------------------------------------------------

				switch ( getScaleMode() ) {
				case SCALE_GRID:
					int theNewViewportHeight = toScaledViewportDimension( aNewValue.doubleValue(), getViewportHeight(), getFieldHeight(), getFieldGap(), _bordersV + _windowDecorationV );
					if ( theNewViewportHeight != -1 ) {
						LOGGER.debug( "Viewport height changed to := " + theNewViewportHeight );
						setViewportHeight( theNewViewportHeight );
					}
					break;
				case SCALE_FIELDS:
					int theNewFieldHeight = toScaledFieldDimension( aNewValue.doubleValue(), getViewportHeight(), getFieldHeight(), getFieldGap(), _bordersV + _windowDecorationV );
					if ( theNewFieldHeight != -1 ) {
						LOGGER.debug( "Field height changed to := " + theNewFieldHeight );
						setFieldHeight( theNewFieldHeight );
					}
					break;
				case NONE:
					break;
				}
			}
		};

		// /////////////////////////////////////////////////////////////////////
		// LIFECYLCE:
		// /////////////////////////////////////////////////////////////////////
		@Override
		public void dispose() {
			_window.widthProperty().removeListener( _onWindowWidthChangedEventHandler );
			_window.heightProperty().removeListener( _onWindowHeightChangedEventHandler );
			_window = null;
		}

		// /////////////////////////////////////////////////////////////////////
		// HELPER:
		// /////////////////////////////////////////////////////////////////////

		private int toScaledViewportDimension( double aNewWindowDim, int aViewportDim, int aFieldDim, int aFieldGap, double aBordersDim ) {
			if ( !Double.isNaN( aBordersDim ) && aNewWindowDim != 1.0 ) {
				double theCheckerboardDim = aNewWindowDim - aBordersDim;
				// Just consider top (left) gap as we drag:
				theCheckerboardDim -= aFieldGap;
				int theViewportDim = (int) Math.round( theCheckerboardDim / (aFieldDim + aFieldGap) );
				if ( theViewportDim != aViewportDim ) {
					try {
						return theViewportDim;
					}
					catch ( VetoRuntimeException e ) {
						fxResizeStage();
					}
				}
			}
			return -1;
		}

		private int toScaledFieldDimension( double aNewWindowDim, int aViewportDim, int aFieldDim, int aFieldGap, double aBordersDim ) {
			if ( !Double.isNaN( aBordersDim ) && aNewWindowDim != 1.0 ) {
				double theCheckerboardDim = aNewWindowDim - aBordersDim;
				// Just consider top (left) gap as we drag:
				theCheckerboardDim -= aFieldGap;
				int theFieldDim = (int) Math.round( (theCheckerboardDim - ((aViewportDim - 1) * aFieldGap)) / aViewportDim );
				if ( theFieldDim != aFieldDim ) {
					try {
						return theFieldDim;
					}
					catch ( VetoRuntimeException e ) {
						fxResizeStage();
					}
				}
			}
			return -1;
		}
	}
}