/*
* Copyright (c) 2008-2010, Matthias Mann
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.matthiasmann.twl;
import de.matthiasmann.twl.model.ButtonModel;
import java.util.ArrayList;
/**
* A radial popup menu with round buttons
*
* @author Matthias Mann
*/
public class RadialPopupMenu extends PopupWindow {
private final ArrayList<RoundButton> buttons;
private int radius;
private int buttonRadius;
private int mouseButton;
int buttonRadiusSqr;
public RadialPopupMenu(Widget owner) {
super(owner);
this.buttons = new ArrayList<RoundButton>();
}
public int getButtonRadius() {
return buttonRadius;
}
public void setButtonRadius(int buttonRadius) {
if(buttonRadius < 0) {
throw new IllegalArgumentException("buttonRadius");
}
this.buttonRadius = buttonRadius;
this.buttonRadiusSqr = buttonRadius * buttonRadius;
invalidateLayout();
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
if(radius < 0) {
throw new IllegalArgumentException("radius");
}
this.radius = radius;
invalidateLayout();
}
public int getMouseButton() {
return mouseButton;
}
/**
* Sets the mouse button to which the buttons of this radial menu should react.
* The default is {@link Event#MOUSE_LBUTTON}
* @param mouseButton the mouse button
*/
public void setMouseButton(int mouseButton) {
if(mouseButton < Event.MOUSE_LBUTTON || mouseButton > Event.MOUSE_RBUTTON) {
throw new IllegalArgumentException("mouseButton");
}
this.mouseButton = mouseButton;
for(int i=0,n=buttons.size() ; i<n ; i++) {
buttons.get(i).setMouseButton(mouseButton);
}
}
public Button addButton(String theme, Runnable cb) {
RoundButton button = new RoundButton();
button.setTheme(theme);
button.addCallback(cb);
button.setMouseButton(mouseButton);
addButton(button);
return button;
}
public void removeButton(Button btn) {
int idx = buttons.indexOf(btn);
if(idx >= 0) {
buttons.remove(idx);
removeChild(btn);
}
}
protected void addButton(RoundButton button) {
if(button == null) {
throw new NullPointerException("button");
}
buttons.add(button);
add(button);
}
@Override
public boolean openPopup() {
if(super.openPopup()) {
if(bindMouseDrag(new Runnable() {
public void run() {
boundDragEventFinished();
}
})) {
setAllButtonsPressed();
}
return true;
}
return false;
}
/**
* Opens the radial popup menu around the specified coordinate
*
* @param centerX the X coordinate of the popup center
* @param centerY the Y coordinate of the popup center
* @return true if the popup was opened
*/
public boolean openPopupAt(int centerX, int centerY) {
if(openPopup()) {
adjustSize();
Widget parent = getParent();
int width = getWidth();
int height = getHeight();
setPosition(
limit(centerX - width/2, parent.getInnerX(), parent.getInnerRight() - width),
limit(centerY - height/2, parent.getInnerY(), parent.getInnerBottom() - height));
return true;
}
return false;
}
protected static int limit(int value, int min, int max) {
if(value < min) {
return min;
}
if(value > max) {
return max;
}
return value;
}
/**
* Opens the radial popup menu around the current mouse position
* and uses the event's mouse button as button activator.
*
* @param evt the {@link Event.Type#MOUSE_BTNDOWN} event
* @return true if the popup was opened
*/
public boolean openPopup(Event evt) {
if(evt.getType() == Event.Type.MOUSE_BTNDOWN) {
setMouseButton(evt.getMouseButton());
return openPopupAt(evt.getMouseX(), evt.getMouseY());
}
return false;
}
@Override
public int getPreferredInnerWidth() {
return 2*(radius + buttonRadius);
}
@Override
public int getPreferredInnerHeight() {
return 2*(radius + buttonRadius);
}
@Override
protected void applyTheme(ThemeInfo themeInfo) {
super.applyTheme(themeInfo);
applyThemeRadialPopupMenu(themeInfo);
}
protected void applyThemeRadialPopupMenu(ThemeInfo themeInfo) {
setRadius(themeInfo.getParameter("radius", 40));
setButtonRadius(themeInfo.getParameter("buttonRadius", 40));
}
@Override
protected void layout() {
layoutRadial();
}
protected void layoutRadial() {
int numButtons = buttons.size();
if(numButtons > 0) {
int centerX = getInnerX() + getInnerWidth()/2;
int centerY = getInnerY() + getInnerHeight()/2;
float toRad = (float)(2.0*Math.PI) / numButtons;
for(int i=0 ; i<numButtons ; i++) {
float rad = i * toRad;
int btnCenterX = centerX + (int)(radius * Math.sin(rad));
int btnCenterY = centerY - (int)(radius * Math.cos(rad));
RoundButton button = buttons.get(i);
button.setPosition(btnCenterX - buttonRadius, btnCenterY - buttonRadius);
button.setSize(2*buttonRadius, 2*buttonRadius);
}
}
}
protected void setAllButtonsPressed() {
for(int i=0,n=buttons.size() ; i<n ; i++) {
ButtonModel model = buttons.get(i).getModel();
model.setPressed(true);
model.setArmed(model.isHover());
}
}
protected void boundDragEventFinished() {
closePopup();
}
protected class RoundButton extends Button {
@Override
public boolean isInside(int x, int y) {
int dx = x - (getX() + getWidth()/2);
int dy = y - (getY() + getHeight()/2);
return dx*dx + dy*dy <= buttonRadiusSqr;
}
}
}