/* * Copyright (C) 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.apps.santatracker.doodles.tilt; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.os.Bundle; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import com.google.android.apps.santatracker.doodles.shared.Actor; import com.google.android.apps.santatracker.doodles.shared.Vector2D; /** * The game view for the swimming game. */ public class SwimmingView extends View { private static final String TAG = SwimmingView.class.getSimpleName(); public static final int WATER_BLUE = 0xffa6ffff; public static final int LINES_BLUE = 0xff00d4d4; private static final int LANE_LINE_WIDTH = 10; public static final int NUM_LANES = 5; private Paint swimmingLinesPaint; private SwimmingModel model; private GestureDetector editorGestureDetector; private ScaleGestureDetector scaleDetector; private GestureListener gestureListener; public SwimmingView(Context context) { this(context, null); } public SwimmingView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwimmingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); swimmingLinesPaint = new Paint(); swimmingLinesPaint.setColor(LINES_BLUE); swimmingLinesPaint.setStyle(Style.FILL); gestureListener = new GestureListener(); editorGestureDetector = new GestureDetector(context, gestureListener); scaleDetector = new ScaleGestureDetector(context, new ScaleListener()); } @Override public void onDraw(Canvas canvas) { if (model == null) { return; } synchronized (model) { // Draw background canvas.drawColor(WATER_BLUE); canvas.save(); canvas.scale(model.camera.scale, model.camera.scale); canvas.translate( -model.camera.position.x + model.cameraShake.position.x, -model.camera.position.y + model.cameraShake.position.y); drawSwimmingLines(canvas); model.drawActors(canvas); canvas.restore(); model.drawUiActors(canvas); } } @Override public boolean onTouchEvent(MotionEvent event) { boolean handledTouch = false; if (SwimmingFragment.editorMode) { // Handle touch events in the editor. // Let the ScaleGestureDetector inspect all events. handledTouch = scaleDetector.onTouchEvent(event); final int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: int index = event.getActionIndex(); // If the user touches the screen, check to see if they have selected a polygon vertex, // which they can then drag around the screen. Vector2D worldCoords = model.camera.getWorldCoords(event.getX(index), event.getY(index)); // Reset selection. gestureListener.selectedActor = null; // model.actors will be sorted by z-index. Iterate over it backwards so that touches on // elements are handled in reverse z-index order (i.e., actors in front will be selected // before actors in back). for (int i = model.actors.size() - 1; i >= 0; i--) { Actor actor = model.actors.get(i); if (actor instanceof Touchable && ((Touchable) actor).canHandleTouchAt(worldCoords, model.camera.scale)) { if (model.collisionMode == (actor instanceof CollisionActor)) { // Only allow interactions with objects within the current selection mode (i.e., // only collision objects in collision mode, only scenery objects in non-collision // mode. gestureListener.selectedActor = actor; ((Touchable) actor).startTouchAt(worldCoords, model.camera.scale); break; } } } break; } handledTouch = editorGestureDetector.onTouchEvent(event) || handledTouch; } else { // Handle in-game touch events. if (model != null) { final int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: model.onTouchDown(); handledTouch = true; break; } } } return handledTouch || super.onTouchEvent(event); } public void setModel(SwimmingModel model) { this.model = model; } private void drawSwimmingLines(Canvas canvas) { int laneWidth = SwimmingModel.LEVEL_WIDTH / NUM_LANES; for (int i = 1; i < NUM_LANES; i++) { canvas.drawRect(laneWidth * i - LANE_LINE_WIDTH / 2.0f, model.camera.yToWorld(0), laneWidth * i + LANE_LINE_WIDTH / 2.0f, model.camera.yToWorld(canvas.getHeight()), swimmingLinesPaint); } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { model.camera.scale *= detector.getScaleFactor(); model.camera.scale = Math.max(0.1f, Math.min(model.camera.scale, 5.0f)); return true; } } private class GestureListener extends SimpleOnGestureListener { public Actor selectedActor; @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Vector2D delta = Vector2D.get(distanceX / model.camera.scale, distanceY / model.camera.scale); if (selectedActor != null) { ((Touchable) selectedActor).handleMoveEvent(delta); } else { model.camera.position.add(delta); } delta.release(); return true; } @Override public void onLongPress(MotionEvent event) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); if (selectedActor != null) { boolean handled = ((Touchable) selectedActor).handleLongPress(); if (!handled) { // If the selected actor doesn't handle the long press, the default behavior is to remove // it. model.actors.remove(selectedActor); } } else { // Long press is not on a touchable actor. Create a new object and place it // where the long press occurred. DialogFragment dialogFragment = new CreateObjectDialogFragment( model.camera.getWorldCoords(event.getX(), event.getY())); dialogFragment.show(((Activity) getContext()).getFragmentManager(), "create_object"); } } } private class CreateObjectDialogFragment extends DialogFragment { private Vector2D center; public CreateObjectDialogFragment(Vector2D center) { this.center = center; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final String[] items; items = new String[model.collisionObjectTypes.size()]; model.collisionObjectTypes.toArray(items); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(com.google.android.apps.santatracker.doodles.R.string.create_object) .setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { model.createActor(center, items[which], getResources()); } }); return builder.create(); } } }