InteractionManager.java

/*
 * Copyright © 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.amaze.ui;

import com.nokia.example.amaze.gestures.SafeGestureEvent;
import com.nokia.example.amaze.gestures.SafeGestureInteractiveZone;
import com.nokia.example.amaze.gestures.SafeGestureListener;
import com.nokia.example.amaze.gestures.SafeGestureRegistrationManager;
import com.nokia.example.amaze.model.GameModel;
import com.nokia.example.amaze.model.MarbleModel;
import com.nokia.example.amaze.sensors.AccelerationProvider;

/**
 * The class for managing all user interactions (acceleration sensor readings
 * and touch UI events).
 */
public class InteractionManager
	implements AccelerationProvider.Listener,
			   DoubleTapDetector.Listener,
			   SafeGestureListener,
			   Menu.Listener
{
	// Constants
	public static final float ACCELERATION_THRESHOLD = 1.5f;
	public final static float ACCELERATION_COEFFICIENT_X = 1.5f;
	public final static float ACCELERATION_COEFFICIENT_Y = 0.3f;
	private final static int MIN_CALIBRATION_VALUE = -6;
	public final static int UNDEFINED = -20;
	private final static int ZOOM_INTERVAL = 10;
	
	// Members
	private final MazeCanvas _mazeCanvas;
	private final GameModel _gameModel;
	private final MarbleModel _marbleModel;
	private DoubleTapDetector _doubleTapDetector = null;
	private double _calibration = UNDEFINED;
	private double _ax;
	private double _ay;
	
	/**
	 * Constructor.
	 */
	public InteractionManager(MazeCanvas mazeCanvas,
							  GameModel gameModel)
	{
		if (mazeCanvas == null || gameModel == null) {
			throw new IllegalArgumentException("Null arguments!");
		}
		
		_mazeCanvas = mazeCanvas;
		_gameModel = gameModel;
		_marbleModel = _gameModel.marble();
		_doubleTapDetector = new DoubleTapDetector(this);
		
		// Register to listen to the pinch event
		// Note: If the device doesn't have multitouch support, this will cause
		// unhandled exception.
		SafeGestureRegistrationManager.setListener(_mazeCanvas, this);

		SafeGestureInteractiveZone gestureZone = new SafeGestureInteractiveZone();
		gestureZone.setGesture(SafeGestureInteractiveZone.GESTURE_PINCH);
		gestureZone.setRectangle(0, 0, _mazeCanvas.getWidth(), _mazeCanvas.getHeight());

		SafeGestureRegistrationManager.register(_mazeCanvas, gestureZone);
	}

	/**
	 * From AccelerationProvider.Listener.
	 */
	public void onDataReceived(double ax, double ay, double az) {
		if (_mazeCanvas.gameState() != MazeCanvas.ONGOING) {
			return;
		}
		
		if (_calibration == UNDEFINED) {
			// Do calibrate now based on the current reading
			if (_mazeCanvas.isPortrait()) {
				setCalibration(-ay);
			}
			else {
				setCalibration(-ax);
			}
		}
		
		if (_mazeCanvas.isPortrait()) {
			_ax = ax;
			_ay = ay + _calibration;
		}
		else {
			_ax = ax + _calibration;
			_ay = ay;
		}
	}
	
	/**
	 * From DoubleTapDetector.
	 */
	public void onDoubleTapDetected() {
		if (!_mazeCanvas.povMode()) {
			_mazeCanvas.resetZoom();
		}
	}
	
	/**
	 * From SafeGestureListener.
	 * 
	 * Handles pinch gestures and changes the distance of the camera based on
	 * the gesture. 
	 */
	public void gestureAction(Object container,
			SafeGestureInteractiveZone gestureInteractiveZone,
			SafeGestureEvent gestureEvent)
	{
		if (!_mazeCanvas.povMode()
				&& gestureEvent.getType() == SafeGestureInteractiveZone.GESTURE_PINCH)
		{
			if (gestureEvent.getPinchDistanceChange() < 0) {
				// If the gesture was inwards, scale smaller
				_mazeCanvas.doZoom(ZOOM_INTERVAL);
			}
			else if (gestureEvent.getPinchDistanceChange() > 0) {
				// If the gesture was outwards, scale larger
				_mazeCanvas.doZoom(-ZOOM_INTERVAL);
			}
		}		
	}
	

	/**
	 * From Menu.Listener.
	 */
	public void onMenuItemSelected(int index) {
		if (_mazeCanvas.gameState() == MazeCanvas.PAUSED) {
			if (index == 0) {
				// Resume
				_mazeCanvas.resume();
			}
			else if (index == 1) {
				// Toggle background
				_mazeCanvas.setBackground(!_mazeCanvas.hasBackground());
				
				MenuItem item = _mazeCanvas.menu().itemAt(1);
				
				if (item != null) {
					item.setText((_mazeCanvas.hasBackground() ? "Set background off"
							: "Set background on"));
				}
			}
			else if (index == 2) {
				// Toggle debug mode
				_mazeCanvas._debugMode = !_mazeCanvas._debugMode;
				
				MenuItem item = _mazeCanvas.menu().itemAt(2);
				
				if (item != null) {
					item.setText((_mazeCanvas._debugMode ? "Switch debug mode off"
							: "Switch debug mode on"));
				}
			}
			else if (index == 3) {
				// Restart the game
				_mazeCanvas.startNewGame();
			}	
		}
	}
	
	/**
	 * Starts listening for accelerometer readings.
	 */
	public void onUIInitialized() {
		AccelerationProvider.getProvider(this);
	}
	
	/**
	 * Sets the accelerometer calibration value for the Y axis.
	 * @param calibrationY The value to set.
	 */
	public void setCalibration(final double calibration) {
		_calibration = calibration;
		
		if (_calibration < MIN_CALIBRATION_VALUE
				&& _calibration != UNDEFINED)
		{
			_calibration = MIN_CALIBRATION_VALUE;
		}		
	}
	
	/** 
	 * @return The current accelerometer calibration value.
	 */
	public final double calibration() {
		return _calibration;
	}
	
	/**
	 * Resets the input state.
	 */
	public void reset() {
		_marbleModel.setVelocity(new float[] { 0f, 0f, 0f });
	}
	
	/**
	 * Handles a key pressed event.
	 * @param key
	 */
	public void onKeyPressed(int key) {
		System.out.println("InteractionManager::onKeyPressed(): " + key);
	}
	
	/**
	 * Handler for GameCanvas::pointerPressed() events.
	 */
	public void onPointerPressed(int x, int y) {
		if (TipBox.visible()) {
			TipBox.hide();
			return;
		}
		
		if (_mazeCanvas.infoDialogVisible()) {
			// TODO: These hard coded values are not good at all and need to
			// be fixed sooner or later.
			if (x > 6 && x < 234 && y > 300 && y < 340) {
				_mazeCanvas.openProjectsLink();
			}
			else {
				_mazeCanvas.setInfoDialogVisible(false);
			}
			
			return;
		}
		
		final int width = _mazeCanvas.getWidth();
		final int height = _mazeCanvas.getHeight();

		if (_mazeCanvas.gameState() == MazeCanvas.NOT_STARTED
				|| _mazeCanvas.gameState() == MazeCanvas.PAUSED
				|| _mazeCanvas.gameState() == MazeCanvas.GAME_OVER)
		{
			if (x > width - 42 && y > height - 42) {
				// Exit button pressed
				_mazeCanvas.setButtonPressed(IconButton.EXIT);
			}
			else if (x < 42 && y > height - 42) {
				// Info button pressed
				_mazeCanvas.setButtonPressed(IconButton.INFO);
			}
			else {
				switch (_mazeCanvas.gameState()) {
				case MazeCanvas.NOT_STARTED: {
					_mazeCanvas.startNewGame();
					break;
				}
				case MazeCanvas.PAUSED: {
					_mazeCanvas.menu().onPressed(x, y);
					break;
				}
				case MazeCanvas.GAME_OVER: {
					_mazeCanvas.cameraAnimator().startAnimation(
							CameraAnimator.LEVEL_RESET_ANIMATION_STEP);
					break;
				}
				}
			}
			
			return;
		}
		
		if (_doubleTapDetector.onTapped(x, y)) {
			// Double tap was detected
			return;
		}
		
		if (x > width - 42 && y > height - 42) {
			// Pause button pressed
			_mazeCanvas.setButtonPressed(IconButton.PAUSE);
		}
		else if (x < 42 && y > height - 42) {
			// View mode button pressed
			// Do nothing yet
		}
	}

	/**
	 * Handler for GameCanvas::pointerReleased() events.
	 */
	public void onPointerReleased(int x, int y) {
		final int width = _mazeCanvas.getWidth();
		final int height = _mazeCanvas.getHeight();

		if (_mazeCanvas.gameState() == MazeCanvas.NOT_STARTED
				|| _mazeCanvas.gameState() == MazeCanvas.PAUSED
				|| _mazeCanvas.gameState() == MazeCanvas.GAME_OVER)
		{
			if (x > width - 42 && y > height - 42) {
				// Do quit
				_mazeCanvas.quit();				
			}
			else if (x < 42 && y > height - 42) {
				// Show info dialog
				_mazeCanvas.setInfoDialogVisible(true);
			}
			else {			
				_mazeCanvas.menu().onReleased(x, y);
			}
			
			_mazeCanvas.setButtonPressed(-1);
			return;
		}
		
		if (_mazeCanvas.gameState() == MazeCanvas.ONGOING) {
			if (x > width - 42 && y > height - 42) {
				// Do pause
				System.out.println("MazeCanvas::pointerReleased(): Do pause");
				_mazeCanvas.pause();
			}
			else if (x < 42 && y > height - 42) {
				// Toggle view mode
				_mazeCanvas.toggleViewMode();
			}
		}
		
		_mazeCanvas.setButtonPressed(-1);
	}
	
	/**
	 * Handler for GameCanvas::pointerDragged() events.
	 */
	public void onPointerDragged(int x, int y) {
		if (_mazeCanvas.gameState() == MazeCanvas.PAUSED) {
			_mazeCanvas.menu().onPressed(x, y);
		}
	}
	
	/**
	 * Handler for GameCanvas::pointerRepeated() events.
	 */
	public void onPointerRepeated(int x, int y) {
		onPointerPressed(x, y);
	}

	// Getters for the calibrated accelerometer values. Used for debugging
	// purposes e.g. displaying the values on the screen.
	public final double ax() { return _ax; }
	public final double ay() { return _ay; }
}