/*
Part of the GUI library for Processing
http://www.lagers.org.uk/g4p/index.html
http://sourceforge.net/projects/g4p/files/?source=navbar
Copyright (c) 2012 Peter Lager
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package automenta.vivisect.gui;
import automenta.vivisect.gui.HotSpot.HSalpha;
import automenta.vivisect.gui.HotSpot.HSmask;
import processing.core.PApplet;
import processing.core.PImage;
import processing.event.MouseEvent;
/**
* Buttons create from this class use a number of images to represent it's
* state. This means that buttons can have an irregular and/or discontinuous
* shape. <br>
* <h3>Determining the control size </h3>
* If when creating the button you specify a particular width and height then
* any images that are not the same size will be scaled to fit without regard
* to the original size or aspect ratio. <br>
*
* If when creating the button you do not specify the width and height then it
* will use the width and height of the 'off-button' image and assume that all the
* other images are the same size. <br>
*
* <h3>The images </h3>
* The image button needs 1 to 3 image files to represent the button states <br>
* OFF mouse is not over button <br>
* OVER mouse is over the button <br>
* DOWN the mouse is over the button and a mouse button is being pressed. <br>
*
* If you only provide one image then this will be used for all states, if you
* provide two then the second image is used for both OVER and DOWN states. <br><br>
*
* If you don't provide a mask file then the button 'hotspot' is represented by any
* non-transparent pixels in the OFF image. If you do provide a mask file then the
* hotspot is defined by any black pixels in the mask image. <br><br>
*
*
* Three types of event can be generated :- <br>
* <b> GEvent.PRESSED GEvent.RELEASED GEvent.CLICKED </b><br>
*
* To simplify event handling the button only fires off CLICKED events
* when the mouse button is pressed and released over the button face
* (the default behaviour). <br>
*
* Using <pre>button1.fireAllEvents(true);</pre> enables the other 2 events
* for button <b>button1</b>. A PRESSED event is created if the mouse button
* is pressed down over the button face, the CLICKED event is then generated
* if the mouse button is released over the button face. Releasing the
* button off the button face creates a RELEASED event. <br>
*
*
* @author Peter Lager
*
*/
public class GImageButton extends GControl {
private static PImage[] errImage = null;
protected PImage[] bimage = null;
protected PImage mask = null;
protected int status;
protected boolean reportAllButtonEvents = false;
/**
* The control size will be set to the size of the image file used for the button OFF state. <br>
* There is no alpha mask file..
*
* @param theApplet
* @param p0
* @param p1
* @param fnames an array of up to 3 image filenames to represent the off/over/down state of the button.
*/
public GImageButton(PApplet theApplet, float p0, float p1, String[] fnames) {
this(theApplet, p0, p1, fnames, null);
}
/**
* The control size will be set to the size of the image file used for the button OFF state. <br>
*
* @param theApplet
* @param p0
* @param p1
* @param fnames an array of up to 3 image filenames to represent the off/over/down state of the button.
* @param fnameMask the alpha mask filename or null if no mask
*/
public GImageButton(PApplet theApplet, float p0, float p1, String[] fnames, String fnameMask) {
super(theApplet, p0, p1);
if(errImage == null)
errImage = ImageManager.loadImage(winApp, new String[] { "err0.png", "err1.png", "err2.png" });
//========================================================================
// First of all load images
// Make sure we have an array of filenames
if(fnames == null || fnames.length == 0)
fnames = new String[] { "err0.png", "err1.png", "err2.png" };
bimage = ImageManager.loadImage(winApp, fnames);
// There should be 3 images if not use as many as possible,
// duplicating the last one if neccessary
if(bimage.length != 3){
PImage[] temp = new PImage[3];
for(int i = 0; i < 3; i++)
temp[i] = bimage[Math.min(i, bimage.length - 1)];
bimage = temp;
}
// Get mask image if available
if(fnameMask != null)
mask = winApp.loadImage(fnameMask);
//========================================================================
//========================================================================
// Now decide whether to resize either the images or the button
if(width > 0 && height > 0){ // Resize images
for(int i = 0; i < bimage.length; i++){
if(bimage[i].width != width || bimage[i].height != height)
bimage[i].resize((int)width, (int)height);
}
if(mask != null && (mask.width != width || mask.height != height))
mask.resize((int)width, (int)height);
}
else { // resize button
resize(bimage[0].width, bimage[0].height);
}
//========================================================================
//========================================================================
// Setup the hotspaots
if(mask != null){ // if we have a mask use it for the hot spot
hotspots = new HotSpot[]{
new HSmask(1, mask)
};
}
else { // no mask then use alpha channel of the OFF image
hotspots = new HotSpot[]{
new HSalpha(1, 0, 0, bimage[0], PApplet.CORNER)
};
}
//========================================================================
z = Z_SLIPPY;
// Now register control with applet
createEventHandler(GUI.applet, "handleButtonEvents",
new Class<?>[]{ GImageButton.class, GEvent.class },
new String[]{ "button", "event" }
);
registeredMethods = DRAW_METHOD | MOUSE_METHOD;
cursorOver = HAND;
GUI.addControl(this);
}
/**
* Create an image button of the size specified by the parameters. <br>
* The images will be resized to fit and there is no alpha mask file.
*
* @param theApplet
* @param p0
* @param p1
* @param p2
* @param p3
* @param fnames an array of up to 3 image filenames to represent the off/over/down state of the button.
*/
public GImageButton(PApplet theApplet, float p0, float p1, float p2, float p3, String[] fnames) {
this(theApplet, p0, p1, p2, p3, fnames, null);
}
/**
* Create an image button of the size specified by the parameters. <br>
* The images will be resized to fit.
*
* @param theApplet
* @param p0
* @param p1
* @param p2
* @param p3
* @param fnames an array of up to 3 image filenames to represent the off/over/down state of the button.
* @param fnameMask the alpha mask filename or null if no mask
*/
public GImageButton(PApplet theApplet, float p0, float p1, float p2, float p3, String[] fnames, String fnameMask) {
super(theApplet, p0, p1, p2, p3);
if(errImage == null)
errImage = ImageManager.loadImage(winApp, new String[] { "err0.png", "err1.png", "err2.png" });
//========================================================================
// First of all load images
// Make sure we have an array of filenames
if(fnames == null || fnames.length == 0)
fnames = new String[] { "err0.png", "err1.png", "err2.png" };
bimage = ImageManager.loadImage(winApp, fnames);
// There should be 3 images if not use as many as possible,
// duplicating the last one if neccessary
if(bimage.length != 3){
PImage[] temp = new PImage[3];
for(int i = 0; i < 3; i++)
temp[i] = bimage[Math.min(i, bimage.length - 1)];
bimage = temp;
}
// Get mask image if available
if(fnameMask != null)
mask = winApp.loadImage(fnameMask);
//========================================================================
//========================================================================
// Now decide whether to resize either the images or the button
if(width > 0 && height > 0){ // Resize images
for(int i = 0; i < bimage.length; i++){
if(bimage[i].width != width || bimage[i].height != height)
bimage[i].resize((int)width, (int)height);
}
if(mask != null && (mask.width != width || mask.height != height))
mask.resize((int)width, (int)height);
}
else { // resize button
resize(bimage[0].width, bimage[0].height);
}
//========================================================================
//========================================================================
// Setup the hotspaots
if(mask != null){ // if we have a mask use it for the hot spot
hotspots = new HotSpot[]{
new HSmask(1, mask)
};
}
else { // no mask then use alpha channel of the OFF image
hotspots = new HotSpot[]{
new HSalpha(1, 0, 0, bimage[0], PApplet.CORNER)
};
}
//========================================================================
z = Z_SLIPPY;
// Now register control with applet
createEventHandler(GUI.applet, "handleButtonEvents",
new Class<?>[]{ GImageButton.class, GEvent.class },
new String[]{ "button", "event" }
);
registeredMethods = DRAW_METHOD | MOUSE_METHOD;
cursorOver = HAND;
GUI.addControl(this);
}
public void draw(){
if(!visible) return;
// Update buffer if invalid
//updateBuffer();
winApp.pushStyle();
winApp.pushMatrix();
// Perform the rotation
applyTransform();
// Move matrix to line up with top-left corner
winApp.translate(-halfWidth, -halfHeight);
// Draw buffer
winApp.imageMode(PApplet.CORNER);
if(alphaLevel < 255)
winApp.tint(TINT_FOR_ALPHA, alphaLevel);
winApp.image(bimage[status], 0, 0);
winApp.popMatrix();
winApp.popStyle();
}
/**
*
* When a mouse button is clicked on a GImageButton it generates the GEvent.CLICKED event. If
* you also want the button to generate GEvent.PRESSED and GEvent.RELEASED events
* then you need the following statement.<br>
* <pre>btnName.fireAllEvents(true); </pre><br>
*
* <pre>
* void handleButtonEvents(void handleButtonEvents(GImageButton button, GEvent event) {
* if(button == btnName && event == GEvent.CLICKED){
* // code for button click event
* }
* </pre> <br>
* Where <pre><b>btnName</b></pre> is the GImageButton identifier (variable name) <br><br>
*
*/
public void mouseEvent(MouseEvent event){
if(!visible || !enabled || !available) return;
calcTransformedOrigin(winApp.getCursorX(), winApp.getCursorY());
currSpot = whichHotSpot(ox, oy);
if(currSpot >= 0 || focusIsWith == this)
cursorIsOver = this;
else if(cursorIsOver == this)
cursorIsOver = null;
switch(event.getAction()){
case MouseEvent.PRESS:
if(focusIsWith != this && currSpot >= 0 && z > focusObjectZ()){
dragging = false;
status = PRESS_CONTROL;
takeFocus();
if(reportAllButtonEvents)
fireEvent(this, GEvent.PRESSED);
}
break;
case MouseEvent.CLICK:
// No need to test for isOver() since if the component has focus
// and the mouse has not moved since MOUSE_PRESSED otherwise we
// would not get the Java MouseEvent.MOUSE_CLICKED event
if(focusIsWith == this){
status = OFF_CONTROL;
loseFocus(null);
dragging = false;
fireEvent(this, GEvent.CLICKED);
}
break;
case MouseEvent.RELEASE:
// if the mouse has moved then release focus otherwise
// MOUSE_CLICKED will handle it
if(focusIsWith == this && dragging){
if(currSpot >= 0)
fireEvent(this, GEvent.CLICKED);
else {
if(reportAllButtonEvents){
fireEvent(this, GEvent.RELEASED);
}
}
dragging = false;
loseFocus(null);
status = OFF_CONTROL;
}
break;
case MouseEvent.MOVE:
// If dragged state will stay as PRESSED
if(currSpot >= 0)
status = OVER_CONTROL;
else
status = OFF_CONTROL;
break;
case MouseEvent.DRAG:
dragging = (focusIsWith == this);
break;
}
}
/**
* If the parameter is true all 3 event types are generated, if false
* only CLICKED events are generated (default behaviour).
* @param all
*/
public void fireAllEvents(boolean all){
reportAllButtonEvents = all;
}
/**
* Enable or disable the ability of the component to generate mouse events.<br>
* If the control is to be disabled when it is clicked then this will force the
* mouse off button image is used.
* @param enable true to enable else false
*/
public void setEnabled(boolean enable){
super.setEnabled(enable);
if(!enable)
status = OFF_CONTROL;
}
}