/* * Copyright (C) 2015 Jorge Ruesga * * 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.ruesga.android.wallpapers.photophase; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.RectF; import android.util.Log; import com.ruesga.android.wallpapers.photophase.model.Disposition; import com.ruesga.android.wallpapers.photophase.preferences.PreferencesProvider.Preferences; import com.ruesga.android.wallpapers.photophase.textures.PhotoPhaseTextureManager; import com.ruesga.android.wallpapers.photophase.transitions.Transition; import com.ruesga.android.wallpapers.photophase.transitions.Transitions; import com.ruesga.android.wallpapers.photophase.transitions.Transitions.TRANSITIONS; import com.ruesga.android.wallpapers.photophase.utils.DispositionUtil; import com.ruesga.android.wallpapers.photophase.utils.Utils; import java.util.ArrayList; import java.util.List; /** * A class that represents the wallpapers with all its photo frames. */ public class PhotoPhaseWallpaperWorld { private static final String TAG = "PhotoPhaseWPWorld"; private static final boolean DEBUG = false; // The frame padding private static final int PHOTO_FRAME_PADDING = 2; private final Context mContext; private final PhotoPhaseTextureManager mTextureManager; private List<PhotoFrame> mPhotoFrames; private List<Transition> mTransitions; private final List<Transition> mUnusedTransitions; private List<Integer> mTransitionsQueue; private List<Integer> mUsedTransitionsQueue; private int mCurrent; private int mWidth; private int mHeight; private boolean mRecycled; private final String[] mPortraitDispositions; private final String[] mLandscapeDispositions; /** * Constructor <code>PhotoPhaseWallpaperWorld</code> * * @param ctx The current context * @param textureManager The texture manager */ public PhotoPhaseWallpaperWorld( Context ctx, PhotoPhaseTextureManager textureManager) { super(); mContext = ctx; mTextureManager = textureManager; mCurrent = -1; mUnusedTransitions = new ArrayList<>(); mRecycled = false; Resources res = ctx.getResources(); mPortraitDispositions = res.getStringArray(R.array.portrait_disposition_templates); mLandscapeDispositions = res.getStringArray(R.array.landscape_disposition_templates); } /** * Method that returns an unused transition for the type of transition * * @param type The type of transition * @return Transition The unused transition */ private Transition getUnusedTransition(TRANSITIONS type) { for (Transition transition : mUnusedTransitions) { if (transition.getType().compareTo(type) == 0) { mUnusedTransitions.remove(transition); return transition; } } return null; } /** * Method that returns or creates a transition for the type of transition * * @param type The type of transition * @return Transition The unused transition */ private Transition getOrCreateTransition(TRANSITIONS type) { Transition transition = getUnusedTransition(type); if (transition == null) { transition = Transitions.createTransition(mContext, mTextureManager, type); } transition.reset(); return transition; } /** * Method that ensures the transitions queue */ private void ensureTransitionsQueue() { if (mTransitionsQueue.isEmpty()) { mTransitionsQueue.addAll(mUsedTransitionsQueue); mUsedTransitionsQueue.clear(); } } /** * Method that selects a transition and assign it to a random photo frame. */ public void selectRandomTransition() { // Ensure queue ensureTransitionsQueue(); if (mTransitionsQueue.isEmpty()) { return; } // Get a random frame to apply the transition to int item = Utils.getNextRandom(0, mTransitionsQueue.size() - 1); int pos = mTransitionsQueue.remove(item); mUsedTransitionsQueue.add(pos); PhotoFrame frame = mPhotoFrames.get(pos); // Select the transition selectTransition(frame, pos); } /** * Method that selects a transition and assign it to the photo frame. * * @param frame The photo frame to select */ public void selectTransition(PhotoFrame frame) { // Ensure queue ensureTransitionsQueue(); // Get a the frame to apply the transition to int pos = mPhotoFrames.indexOf(frame); if (pos == -1) { return; } mTransitionsQueue.remove(Integer.valueOf(pos)); mUsedTransitionsQueue.add(pos); // Select the transition selectTransition(frame, pos); } /** * Method that selects a transition and assign it to a photo frame. * * @param frame The frame to select * @param pos The position */ private void selectTransition(PhotoFrame frame, int pos) { // Create or use a transition Transition transition = null; boolean isSelectable = false; while (transition == null || !isSelectable) { boolean isRandom = Preferences.General.Transitions.getSelectedTransitions(mContext).isEmpty(); TRANSITIONS type = Transitions.getNextTypeOfTransition(mContext); transition = getOrCreateTransition(type); isSelectable = transition.isSelectable(frame); if (!isSelectable) { mUnusedTransitions.add(transition); if (!isRandom) { // If is not possible to select a valid transition then select a swap // transition (this one doesn't relies on any selection) transition = getOrCreateTransition(TRANSITIONS.SWAP); isSelectable = true; } } } mTransitions.set(pos, transition); transition.select(frame); mCurrent = pos; } /** * Method that deselect the current transition. * * @param matrix The model-view-projection matrix * @param offset The current x offset */ public void deselectTransition(float[] matrix, float offset) { if (mCurrent != -1 && mCurrent < mTransitions.size()) { // Retrieve the finally target Transition currentTransition = mTransitions.get(mCurrent); PhotoFrame currentTarget = currentTransition.getTarget(); PhotoFrame finalTarget = currentTransition.getTransitionTarget(); mUnusedTransitions.add(currentTransition); if (finalTarget != null) { Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION); mTransitions.set(mCurrent, transition); currentTarget.recycle(); mPhotoFrames.set(mCurrent, finalTarget); transition.select(finalTarget); // Draw the transition once transition.apply(matrix, offset); } mCurrent = -1; } } /** * Method that removes all internal references. */ public void recycle() { // Destroy the previous world if (mTransitions != null) { int cc = mTransitions.size() - 1; for (int i = cc; i >= 0; i--) { Transition transition = mTransitions.get(i); transition.recycle(); mTransitions.remove(i); } } mCurrent = -1; if (mUnusedTransitions != null) { int cc = mUnusedTransitions.size() - 1; for (int i = cc; i >= 0; i--) { Transition transition = mUnusedTransitions.get(i); transition.recycle(); mUnusedTransitions.remove(i); } } if (mTransitionsQueue != null) { mTransitionsQueue.clear(); } if (mUsedTransitionsQueue != null) { mUsedTransitionsQueue.clear(); } mRecycled = true; } /** * Method that returns if there are any transition running in the world. * * @return boolean If there are any transition running in the world */ public boolean hasRunningTransition() { if (mTransitions != null) { for (Transition transition : mTransitions) { if (transition.isRunning()) { return true; } } } return false; } /** * Method that creates and fills the world with {@link PhotoFrame} objects. * * @param w The new width dimension * @param h The new height dimension */ public synchronized void recreateWorld(int w, int h) { if (DEBUG) Log.d(TAG, "Recreating the world. New surface: " + w + "x" + h); // Destroy the previous world if (mRecycled) { recycle(); mRecycled = false; } // Save the new dimensions of the wallpaper mWidth = w; mHeight = h; // Calculate the new world int orientation = mContext.getResources().getConfiguration().orientation; boolean portrait = orientation == Configuration.ORIENTATION_PORTRAIT; int cols = portrait ? Preferences.Layout.getCols(mContext) : Preferences.Layout.getRows(mContext); int rows = portrait ? Preferences.Layout.getRows(mContext) : Preferences.Layout.getCols(mContext); float cellw = 2.0f / cols; float cellh = 2.0f / rows; List<Disposition> dispositions = getWorldDispositions(portrait); if (DEBUG) { Log.d(TAG, "Dispositions: " + dispositions.size() + " | " + String.valueOf(dispositions)); } mPhotoFrames = new ArrayList<>(dispositions.size()); mTransitions = new ArrayList<>(dispositions.size()); mTransitionsQueue = new ArrayList<>(dispositions.size()); mUsedTransitionsQueue = new ArrayList<>(dispositions.size()); int i = 0; int count = dispositions.size(); for (Disposition disposition : dispositions) { // Create the photo frame float[] frameVertices = getVerticesFromDisposition(disposition, cellw, cellh); float[] photoVertices = getFramePadding(frameVertices, portrait ? w : h, portrait ? h : w, count > 1); PhotoFrame frame = new PhotoFrame( disposition, mTextureManager, frameVertices, photoVertices, Colors.getInstance(mContext).getBackground()); mPhotoFrames.add(frame); // Assign a null transition to the photo frame Transition transition = getOrCreateTransition(TRANSITIONS.NO_TRANSITION); transition.select(frame); mTransitions.add(transition); if (disposition.hasFlag(Disposition.BACKGROUND_FLAG) && disposition.hasFlag(Disposition.TRANSITION_FLAG)) { mTransitionsQueue.add(i); } i++; } } /** * Method that returns a photo frame from a coordinates in screen * * @param coordinates The coordinates * @return The photo frame reference or null if none found */ public PhotoFrame getFrameFromCoordinates(PointF coordinates) { // Translate pixels coordinates to GLES coordinates float tx = ((coordinates.x * 2) / mWidth) - 1; float ty = (((coordinates.y * 2) / mHeight) - 1) * -1; // Locate the frame for (PhotoFrame frame : mPhotoFrames) { RectF vertex = Utils.rectFromVertex(frame.getPhotoVertex()); if (vertex.left < tx && vertex.right > tx && vertex.top > ty && vertex.bottom < ty) { return frame; } } return null; } /** * Method that draws all the photo frames. * * @param matrix The model-view-projection matrix * @param offset The current x offset */ public void draw(float[] matrix, float offset) { // Apply every transition if (mTransitions != null) { // First draw the non-running transitions; then the active ones for (Transition transition : mTransitions) { // Don't draw frames with no background flagged if (!transition.getTarget().getDisposition().hasFlag(Disposition.BACKGROUND_FLAG)) { continue; } if (!transition.isRunning()) { transition.apply(matrix, offset); } } for (Transition transition : mTransitions) { // Don't draw frames with no background flagged if (!transition.getTarget().getDisposition().hasFlag(Disposition.BACKGROUND_FLAG)) { continue; } if (transition.isRunning()) { transition.apply(matrix, offset); } } } } /** * Method that returns a coordinates per vertex array from a disposition * * @param disposition The source disposition * @param cellw The cell width based on the surface * @param cellh The cell height based on the surface * @return float[] The coordinates per vertex array */ private static float[] getVerticesFromDisposition( Disposition disposition, float cellw, float cellh) { return new float[] { // bottom left -1.0f + (disposition.x * cellw), 1.0f - ((disposition.y * cellh) + (disposition.h * cellh)), // bottom right -1.0f + ((disposition.x * cellw) + (disposition.w * cellw)), 1.0f - ((disposition.y * cellh) + (disposition.h * cellh)), // top left -1.0f + (disposition.x * cellw), 1.0f - (disposition.y * cellh), // top right -1.0f + ((disposition.x * cellw) + (disposition.w * cellw)), 1.0f - (disposition.y * cellh) }; } /** * Method that applies a padding to the frame * * @param coords The source coordinates * @param screenWidth The screen width * @param screenHeight The screen height * @return float[] The new coordinates */ private float[] getFramePadding(float[] coords, int screenWidth, int screenHeight, boolean needsFramePadding) { float[] paddingCoords = new float[coords.length]; System.arraycopy(coords, 0, paddingCoords, 0, coords.length); if (needsFramePadding && Preferences.General.isFrameSpacer(mContext)) { final float pxw = (1 / (float) screenWidth) * PHOTO_FRAME_PADDING; final float pxh = (1 / (float) screenHeight) * PHOTO_FRAME_PADDING; paddingCoords[0] += pxw; paddingCoords[1] += pxh; paddingCoords[2] -= pxw; paddingCoords[3] += pxh; paddingCoords[4] += pxw; paddingCoords[5] -= pxh; paddingCoords[6] -= pxw; paddingCoords[7] -= pxh; } return paddingCoords; } /** * Method that returns the dispositions to draw in the world * * @param portrait If the orientation is portrait (true) or landscape (false) * @return List<Disposition> The list of dispositions */ private List<Disposition> getWorldDispositions(boolean portrait) { // If user selected a random disposition, then use one of the predefined layouts if (Preferences.Layout.isRandomDispositions(mContext)) { // Random if (portrait) { // Portrait int next = Utils.getNextRandom(0, mPortraitDispositions.length -1); return DispositionUtil.toDispositions(mPortraitDispositions[next]); } // Landscape int next = Utils.getNextRandom(0, mLandscapeDispositions.length -1); return DispositionUtil.toDispositions(mLandscapeDispositions[next]); } // User-defined return portrait ? Preferences.Layout.getPortraitDisposition(mContext) : Preferences.Layout.getLandscapeDisposition(mContext); } }