WheelMenu.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.drumkit.components;

import com.nokia.example.drumkit.helpers.ImageLoader;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Round menu component
 */
public class WheelMenu
        implements Button.Listener {

    private static final int BUTTON_COUNT = 12;
    private static final int MAX_RADIUS = 80;
    private static final int UPDATE_INTERVAL = 30;
    private static final int DELTA_RADIUS = 8; // pixels
    private static final float ANGLE = (float) (2 * Math.PI / BUTTON_COUNT);
    private int radius = 0;
    private int btnWidth = 49;
    private int btnHeight = 49;
    private int originX = 0;
    private int originY = 0;
    private int destinationX = 0;
    private int destinationY = 0;
    private boolean visible = false;
    private boolean open = false;
    private Button selected;
    private Button[] buttons = new Button[12];
    private Timer animator;
    private final Image tapToSelect;
    private final Listener listener;

    /**
     * Interface for listening wheel events
     */
    public interface Listener {

        void wheelOpened();

        void wheelClosed();

        void wheelItemSelected(Button button);
    }

    public WheelMenu(Listener listener) {
        this.listener = listener;
        createButtons();
        animator = new Timer();

        tapToSelect = ImageLoader.loadImage("/tap.png");
    }

    /**
     * Create buttons for the wheel menu
     */
    private void createButtons() {
        buttons[0] = new Button("/kick.png", "/kick_highlight.png", "/kick.png", this);
        buttons[1] = new Button("/snare.png", "/snare_highlight.png", "/snare.png", this);
        buttons[2] = new Button("/cowbell.png", "/cowbell_highlight.png", "/cowbell.png", this);
        buttons[3] = new Button("/hihat2.png", "/hihat2_highlight.png", "/hihat2.png", this);
        buttons[4] = new Button("/hihat1.png", "/hihat1_highlight.png", "/hihat1.png", this);
        buttons[5] = new Button("/crash.png", "/crash_highlight.png", "/crash.png", this);
        buttons[6] = new Button("/splash.png", "/splash_highlight.png", "/splash.png", this);
        buttons[7] = new Button("/ride2.png", "/ride2_highlight.png", "/ride2.png", this);
        buttons[8] = new Button("/ride1.png", "/ride1_highlight.png", "/ride1.png", this);
        buttons[9] = new Button("/tom1.png", "/tom1_highlight.png", "/tom1.png", this);
        buttons[10] = new Button("/tom2.png", "/tom2_highlight.png", "/tom2.png", this);
        buttons[11] = new Button("/tom3.png", "/tom3_highlight.png", "/tom3.png", this);
        // The third (disabled) image is actually not used with wheel

        for (int i = 0; i < buttons.length; i++) {
            buttons[i].setSelectable(true);
        }
    }

    /**
     * Show wheel menu
     */
    public void show() {
        animator.cancel();
        animator = new Timer();
        TimerTask grow = new TimerTask() {

            public void run() {
                if (radius < MAX_RADIUS) {
                    radius += DELTA_RADIUS;
                    updateWheel();
                }
                else {
                    listener.wheelOpened();
                    open = true;
                    this.cancel();
                }
            }
        };
        visible = true;
        animator.schedule(grow, 0, UPDATE_INTERVAL);
    }

    /**
     * Hide wheel menu
     */
    public void hide() {
        open = false;
        animator.cancel();
        animator = new Timer();
        TimerTask shrink = new TimerTask() {

            public void run() {
                if (radius > 0) {
                    radius -= DELTA_RADIUS;
                    updateWheel();
                }
                else {
                    radius = 0;
                    visible = false;
                    listener.wheelClosed();
                    this.cancel();
                }
            }
        };
        animator.schedule(shrink, 0, UPDATE_INTERVAL);
    }

    /**
     * Calculate button positions
     */
    public void updateWheel() {
        float ratio = (float) radius / MAX_RADIUS;
        int newX = originX + (int) ((float) (destinationX - originX) * ratio);
        int newY = originY + (int) ((float) (destinationY - originY) * ratio);
        int btnX = 0;
        int btnY = 0;

        // AngleExtra is used to get the spinning effect
        float angleExtra = 8 * ratio;
        for (int i = 0; i < BUTTON_COUNT; i++) {
            btnX = (int) (Math.cos(i * ANGLE + angleExtra) * radius);
            btnY = (int) (Math.sin(i * ANGLE + angleExtra) * radius);
            buttons[i].setPosition(btnX - btnWidth / 2 + newX,
                                   btnY - btnHeight / 2 + newY);
        }
    }

    /**
     * Render wheel menu
     * @param g Graphics
     */
    public void paint(Graphics g) {
        if (visible) {
            for (int i = 0; i < BUTTON_COUNT; i++) {
                buttons[i].paint(g);
            }
            if (open) {
                g.drawImage(tapToSelect, destinationX - tapToSelect.getWidth() / 2,
                            destinationY - tapToSelect.getHeight() / 2, Graphics.TOP | Graphics.LEFT);
            }
        }
    }

    /**
     * Handles pressed events
     */
    public void pointerPressed(int x, int y) {
        if (open) {
            int dX = destinationX - x;
            int dY = destinationY - y;
            int distance = (int) Math.sqrt(dX * dX + dY * dY);
            if (distance < tapToSelect.getWidth() / 3) {
                hide();
            }
            else {
                for (int i = 0; i < BUTTON_COUNT; i++) {
                    if (buttons[i] != selected) {
                        buttons[i].pointerPressed(x, y);
                    }
                }
            }
        }
    }

    /**
     * Handles dragged events
     */
    public void pointerDragged(int x, int y) {
        if (open) {
            for (int i = 0; i < BUTTON_COUNT; i++) {
                if (buttons[i] != selected) {
                    buttons[i].pointerDragged(x, y);
                }
            }
        }
    }

    /**
     * Handles released events
     */
    public void pointerReleased(int x, int y) {
        if (open) {
            for (int i = 0; i < BUTTON_COUNT; i++) {
                if (buttons[i] != selected) {
                    buttons[i].pointerReleased(x, y);
                }
            }
        }
    }

    public boolean isVisible() {
        return visible;
    }

    /**
     * Center coordinate for wheel menu origin
     * @param x
     * @param y
     */
    public void setOrigin(int x, int y) {
        this.originX = x;
        this.originY = y;
    }

    /**
     * Center coordinate for wheel menu destination
     * @param x
     * @param y
     */
    public void setDestination(int x, int y) {
        this.destinationX = x;
        this.destinationY = y;
    }

    /**
     * Get index of the selected toggle button
     * @return Index
     */
    public int getSelectedIndex() {
        int index = -1;
        for (int i = 0; i < buttons.length; i++) {
            if (buttons[i] == selected) {
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * Select menu item
     */
    public void select(int index) {
        if (selected != null) {
            selected.deslect();
        }
        selected = buttons[index];
        selected.select();
    }

    /**
     * Handle clicks of menu buttons
     * @param button Source button
     */
    public void clicked(Button button) {
        if (selected != null) {
            selected.deslect();
        }
        selected = button;
        listener.wheelItemSelected(button);
    }
}