/* * Copyright © 2011 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.wordpress.views; import com.nokia.example.wordpress.helpers.ImageLoader; import java.io.IOException; import java.util.Hashtable; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; import javax.microedition.lcdui.game.Sprite; /** * Class for handling application views. Handles view switching, drawing * and animations. Displaying is done using GameCanvas. */ public class ViewMaster extends GameCanvas { public static final int VIEW_NOVIEW = -1; public static final int VIEW_SPLASH = 0; public static final int VIEW_LOADER = 1; public static final int VIEW_POSTS = 2; public static final int VIEW_SINGLEPOST = 3; public static final int VIEW_COMMENTSFORPOST = 4; public static final int VIEW_SINGLECOMMENT = 5; public static final int VIEW_COMMENTS = 6; public static final int VIEW_NEWPOST = 7; public static final int VIEW_NEWCOMMENT = 8; public static final int VIEW_LOGIN = 9; private static ViewMaster self = null; private BaseView splashView; private BaseView loaderView; private BaseView postsView; private BaseView singlePostView; private BaseView commentsForPostView; private BaseView singleCommentView; private BaseView commentsView; private BaseView newPostView; private BaseView newCommentView; private BaseView loginView; public BaseView activeView = null; private Graphics g; private boolean initialized = false; private int width; private int height; private Image backgroundImage; public Hashtable gravatarCache = new Hashtable(); public Image defaultGravatarIcon; private Vector viewStack = new Vector(); private Timer timer; private boolean animationInProgress = false; private float animationProgress = 0f; private float animationSpeed = 0.1f; private int animationDirection = 1; /** * The front buffer, the active view is drawn here and then flushed * to the main display. */ private Image frontImage; private Graphics frontGraphics; /** * The back buffer, the view that is going to be deactivated is drawn here. * This and the frontImage are used for view transition animation. */ private Image backImage; private Graphics backGraphics; public ViewMaster() { super(false); this.setFullScreenMode(true); timer = new Timer(); } public static ViewMaster getInstance() { if (self == null) { self = new ViewMaster(); } return self; } protected void showNotify() { if (initialized) { return; } // At this point the final dimensions are available. width = getWidth(); height = getHeight(); g = getGraphics(); try { backgroundImage = ImageLoader.getInstance().loadImage("/background.png"); defaultGravatarIcon = ImageLoader.getInstance().loadImage("/avatar.png"); } catch (IOException e) { } // Create front and backbuffers. frontImage = Image.createImage(width, height); frontGraphics = frontImage.getGraphics(); backImage = Image.createImage(width, height); backGraphics = backImage.getGraphics(); // Create all views. splashView = new SplashView(g, 0, 0, width, height); loaderView = new LoaderView(g, 0, 0, width, height); postsView = new PostsView(g, 0, 0, width, height); singlePostView = new SinglePostView(g, 0, 0, width, height); commentsForPostView = new CommentsForPostView(g, 0, 0, width, height); singleCommentView = new SingleCommentView(g, 0, 0, width, height); commentsView = new CommentsView(g, 0, 0, width, height); newPostView = new NewPostView(g, 0, 0, width, height); newCommentView = new NewCommentView(g, 0, 0, width, height); loginView = new LoginView(g, 0, 0, width, height); initialized = true; // The initial view is the splash view. setView(VIEW_SPLASH); // Set up a timer for doing animations. TimerTask timerTask = new TimerTask() { public void run() { if (animationInProgress) { // View transition animation. animate(); } else { if (activeView != null) { // Active view animation. activeView.animate(); } } } }; timer.schedule(timerTask, 0, 50); } /** * Sets a view to be displayed. Transitions the previous view away. * @param viewId */ public void setView(int viewId) { System.out.println("setView " + viewId); if (animationInProgress) { // Do nothing when already transitioning. return; } if (activeView != null) { if (activeView.id() == viewId) { // Trying to activate a view that's already active. return; } else { // Draw the previous view into the back buffer. activeView.setGraphics(backGraphics); synchronized (backGraphics) { // Synchronizeddraw due to the animation timer thread. activeView.draw(0, 0, width, height); } activeView.deactivate(); } } switch (viewId) { case VIEW_SPLASH: activeView = splashView; break; case VIEW_LOADER: activeView = loaderView; break; case VIEW_POSTS: activeView = postsView; break; case VIEW_SINGLEPOST: activeView = singlePostView; break; case VIEW_COMMENTSFORPOST: activeView = commentsForPostView; break; case VIEW_SINGLECOMMENT: activeView = singleCommentView; break; case VIEW_COMMENTS: activeView = commentsView; break; case VIEW_NEWPOST: activeView = newPostView; break; case VIEW_NEWCOMMENT: activeView = newCommentView; break; case VIEW_LOGIN: activeView = loginView; break; } // Push the view id into viewstack. viewStack.addElement(new Integer(activeView.id())); activeView.setGraphics(frontGraphics); activeView.activate(); // Skip transition animation for some views for better user experience. if (activeView.id() == VIEW_SPLASH || activeView.id() == VIEW_LOGIN || activeView.id() == VIEW_LOADER) { draw(); } else { startAnimation(); } } /** * Activate the previous view from the stack. * At least two views need to be there. */ public void setPreviousView() { if (viewStack.size() >= 2) { // First remove and forget the current view id. viewStack.removeElement(viewStack.lastElement()); // Remove the 2nd to last, which we want to activate. int viewId = ((Integer) viewStack.lastElement()).intValue(); viewStack.removeElement(viewStack.lastElement()); if (viewId == VIEW_NEWPOST || viewId == VIEW_NEWCOMMENT) { // Backing into one of these is not a good idea, so skip // to an even older view. viewId = ((Integer) viewStack.lastElement()).intValue(); viewStack.removeElement(viewStack.lastElement()); } // Activate and push the view id back. setView(viewId); } } /** * Retrieves the id of the previously active view. * @return */ public int getPreviousViewId() { if (viewStack.size() < 2) { return VIEW_NOVIEW; } else { // Access the second to last element. Integer viewId = (Integer) viewStack.elementAt(viewStack.size()-2); return viewId.intValue(); } } /** * Draws the active view. */ public void draw() { if (!initialized || activeView == null || animationInProgress) { return; } synchronized (frontGraphics) { // Synchronized draw due to the animation timer thread. activeView.draw(0, 0, width, height); } g.drawImage(frontImage, 0, 0, g.TOP | g.LEFT); flushGraphics(); } /** * Updates a part of screen from the front buffer. * @param x * @param y * @param width * @param height */ public void updateScreen(int x, int y, int width, int height) { g.drawImage(frontImage, 0, 0, g.TOP | g.LEFT); flushGraphics(x, y, width, height); } /** * Starts the view transition animation. */ private void startAnimation() { // Change the direction every time. animationDirection = -animationDirection; // Prepare front buffer, protect from the animation timer. synchronized (frontGraphics) { activeView.draw(0, 0, width, height); } // Update the tab area first, as animating it looks disorienting. g.drawImage(frontImage, 0, 0, g.TOP | g.LEFT); flushGraphics(0, 0, width, activeView.tabsHeight()); animationInProgress = true; animationProgress = 0f; } /** * Run the view transition animation step by step. */ private void animate() { animationProgress += animationSpeed; if (animationProgress >= 1f) { // Animation finished. Draw the finished state on screen. animationProgress = 1f; animationInProgress = false; g.drawImage(frontImage, 0, 0, g.TOP | g.LEFT); flushGraphics(); // Text editor based views would like to know when // they can display the text editors. activeView.transitionFinished(); return; } // Calculate the position on a quadratic curve and determine // the placements for the front and back buffers. float progress = -animationProgress * (animationProgress - 2); int xPos = (int) (((float) width) * progress); int frontX = -width + xPos; int backX = xPos; if (animationDirection > 0) { frontX = width - xPos; backX = -xPos; } // Draw the buffers and display. g.drawImage(frontImage, frontX, 0, g.TOP | g.LEFT); g.drawImage(backImage, backX, 0, g.TOP | g.LEFT); flushGraphics(0, activeView.tabsHeight(), width, height - activeView.tabsHeight()); } /** * Key pressed event handler. * @param keyCode */ protected void keyPressed(int keyCode) { if (activeView == null) { return; } activeView.keyPressed(keyCode); } /** * Key released event handler. * @param keyCode */ protected void keyReleased(int keyCode) { if (activeView == null) { return; } activeView.keyReleased(keyCode); } /** * Utility to draw the background image. This is used to draw the background * and list highlights. * @param g * @param x * @param y * @param width * @param height * @param highlight Set to true to have a highlighted effect. */ public void drawBackground(Graphics g, int x, int y, int width, int height, boolean highlight) { // Check whether the image is big enough. if (x + width >= backgroundImage.getWidth() || y + height >= backgroundImage.getHeight()) { g.setColor(Visual.BACKGROUND_COLOR); g.fillRect(x, y, width, height); } else { g.drawRegion(backgroundImage, x, y, width, height, Sprite.TRANS_NONE, x, y, g.TOP | g.LEFT); } if (highlight) { drawDimmedBackground(g, x, y, width, height); } } /** * Dim the background when using the progress indicator on top. */ public void drawDimmedBackground() { drawDimmedBackground(g, 0, 0, width, height); flushGraphics(); } /** * Render a part of the background dimmed. Used for drawing list focus cursor. * @param g * @param x * @param y * @param width * @param height */ private void drawDimmedBackground(Graphics g, int x, int y, int width, int height) { // This would be more optimized if the alpha mask were pregenerated. int[] pixels = new int[width * height]; for (int i = 0; i < width * height; i++) { //pixels[i] = 0xaa000000; pixels[i] = 0x55000000; } g.drawRGB(pixels, 0, width, x, y, width, height, true); pixels = null; } }