com.codename1.components.OnOffSwitch Maven / Gradle / Ivy
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.components;
import com.codename1.ui.CheckBox;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.ActionSource;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.EventDispatcher;
import java.util.Collection;
import java.util.Vector;
/**
* The on/off switch is a checkbox of sort (although it derives container) that represents its state as
* a switch each of which has a short label associated with it.
* It has two types: Android and iOS. The types differ in the way that they are rendered.
* The Android type (the default) is just a button with a label that can be moved/dragged between
* the two states. The iOS version is more elaborate due to the look of that platform.
*
*
* @author Shai Almog
* @deprecated Use new {@link Switch} class instead as it conforms to the latest platform looks and feels.
*/
public class OnOffSwitch extends Container implements ActionSource {
private String on = "ON";
private String off = "OFF";
private boolean iosMode;
private boolean noTextMode;
private boolean value;
private CheckBox button;
private boolean dragged;
private int pressX;
private int buttonWidth;
private Image switchOnImage;
private Image switchOffImage;
private Image switchMaskImage;
private int deltaX;
private EventDispatcher dispatcher = new EventDispatcher();
private boolean animationLock;
/**
* Default constructor
*/
public OnOffSwitch() {
setUIID("OnOffSwitch");
initialize();
}
/**
* {@inheritDoc}
*/
protected Dimension calcPreferredSize() {
if(iosMode) {
if(switchMaskImage == null) {
switchMaskImage = UIManager.getInstance().getThemeImageConstant("switchMaskImage");
if(switchMaskImage == null) {
return super.calcPreferredSize();
}
}
return new Dimension(switchMaskImage.getWidth(), switchMaskImage.getHeight());
}
return super.calcPreferredSize();
}
/**
* {@inheritDoc}
*/
protected void resetFocusable() {
setFocusable(true);
}
private void initialize() {
iosMode = UIManager.getInstance().isThemeConstant("onOffIOSModeBool", false);
removeAll();
setFocusable(true);
if(iosMode) {
button = null;
switchMaskImage = UIManager.getInstance().getThemeImageConstant("switchMaskImage");
switchOnImage = UIManager.getInstance().getThemeImageConstant("switchOnImage");
switchOffImage = UIManager.getInstance().getThemeImageConstant("switchOffImage");
noTextMode = UIManager.getInstance().isThemeConstant("noTextModeBool", false);
} else {
setLayout(new BoxLayout(BoxLayout.Y_AXIS));
button = new CheckBox(on);
button.setToggle(true);
button.setUIID("Button");
button.setEndsWith3Points(false);
button.getUnselectedStyle().setFont(getUnselectedStyle().getFont());
button.getSelectedStyle().setFont(getSelectedStyle().getFont());
button.getPressedStyle().setFont(getSelectedStyle().getFont());
Dimension d = button.getPreferredSize();
button.setText(off);
int pw = button.getPreferredW();
d.setWidth(Math.max(pw, d.getWidth()));
// prevents the button from growing/shrinking as its states flip
button.setPreferredSize(d);
buttonWidth = button.getPreferredW();
button.setFocusable(false);
updateButton();
addComponent(button);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
flip();
}
});
}
}
/**
* {@inheritDoc}
*/
protected boolean isStickyDrag() {
return true;
}
/**
* Adds a listener to the switch which will cause an event to dispatch on click
*
* @param l implementation of the action listener interface
*/
public void addActionListener(ActionListener l){
dispatcher.addListener(l);
}
/**
* Removes the given action listener from the switch
*
* @param l implementation of the action listener interface
*/
public void removeActionListener(ActionListener l){
dispatcher.removeListener(l);
}
/**
* Returns a vector containing the action listeners for this button
* @return the action listeners
* @deprecated use the version that returns a collection
*/
public Vector getActionListeners() {
return dispatcher.getListenerVector();
}
/**
* Returns a collection containing the action listeners for this button
* @return the action listeners
*/
public Collection getListeners() {
return dispatcher.getListenerCollection();
}
private void fireActionEvent(){
dispatcher.fireActionEvent(new ActionEvent(this,ActionEvent.Type.PointerPressed));
Display d = Display.getInstance();
if(d.isBuiltinSoundsEnabled()) {
d.playBuiltinSound(Display.SOUND_TYPE_BUTTON_PRESS);
}
}
/**
* {@inheritDoc}
*/
public void paint(Graphics g) {
if(iosMode) {
int switchButtonPadInt = UIManager.getInstance().getThemeConstant("switchButtonPadInt", 16);
if(Display.getInstance().getDisplayWidth() > 480) {
// is retina
switchButtonPadInt *= 2;
}
Style s = getStyle();
int x = getX() + s.getPaddingLeftNoRTL();
int y = getY() + s.getPaddingTop();
if(!value) {
if(deltaX > 0) {
dragged = false;
} else {
if(deltaX < -switchOnImage.getWidth()) {
deltaX = -switchOnImage.getWidth();
}
}
} else {
if(deltaX < 0) {
dragged = false;
} else {
if(deltaX > switchOnImage.getWidth()) {
deltaX = switchOnImage.getWidth();
}
}
}
if(dragged) {
int onX;
int offX;
if(value) {
onX = x - deltaX;
offX = x - deltaX + switchOnImage.getWidth() - 2*switchButtonPadInt;
} else {
onX = x - deltaX - switchOnImage.getWidth() + 2*switchButtonPadInt;
offX = x - deltaX;
}
switchButtonPadInt /= 2;
int oldClipX = g.getClipX();
int oldClipY = g.getClipY();
int oldClipW = g.getClipWidth();
int oldClipH = g.getClipHeight();
g.clipRect(getX(), getY(), switchMaskImage.getWidth(), switchMaskImage.getHeight());
g.drawImage(switchOnImage, onX, y);
g.drawImage(switchOffImage, offX, y);
int strWidth = s.getFont().stringWidth(on);
int sX = onX + switchMaskImage.getWidth() / 2 - strWidth / 2 - switchButtonPadInt;
int sY = y + switchMaskImage.getHeight() / 2 - s.getFont().getHeight() / 2;
g.setFont(s.getFont());
g.setColor(0xffffff);
int alpha = g.concatenateAlpha(s.getFgAlpha());
if (!noTextMode) {
g.drawString(on, sX, sY, Style.TEXT_DECORATION_3D);
}
strWidth = s.getFont().stringWidth(off);
g.setColor(0x333333);
sX = offX + switchMaskImage.getWidth() / 2 - strWidth / 2 + switchButtonPadInt;
if(!noTextMode) {
g.drawString(off, sX, sY);
}
g.setAlpha(alpha);
g.setClip(oldClipX, oldClipY, oldClipW, oldClipH);
} else {
String str;
switchButtonPadInt /= 2;
int alpha = g.concatenateAlpha(s.getFgAlpha());
if(value) {
g.drawImage(switchOnImage, x, y);
str = on;
g.setColor(0xffffff);
switchButtonPadInt *= -1;
} else {
g.drawImage(switchOffImage, x, y);
str = off;
g.setColor(0x333333);
}
int strWidth = s.getFont().stringWidth(str);
int sX = x + switchMaskImage.getWidth() / 2 - strWidth / 2 + switchButtonPadInt;
int sY = y + switchMaskImage.getHeight() / 2 - s.getFont().getHeight() / 2;
g.setFont(s.getFont());
if(!noTextMode) {
g.drawString(str, sX, sY);
}
g.setAlpha(alpha);
}
g.drawImage(switchMaskImage, x, y);
} else {
super.paint(g);
}
}
private void updateButton() {
if(value) {
button.setText(on);
getUnselectedStyle().setPadding(LEFT, buttonWidth);
getUnselectedStyle().setPadding(RIGHT, 0);
getSelectedStyle().setPadding(LEFT, buttonWidth);
getSelectedStyle().setPadding(RIGHT, 0);
} else {
button.setText(off);
getUnselectedStyle().setPadding(RIGHT, buttonWidth);
getUnselectedStyle().setPadding(LEFT, 0);
getSelectedStyle().setPadding(RIGHT, buttonWidth);
getSelectedStyle().setPadding(LEFT, 0);
}
}
private void flip() {
setValue(!value);
}
/**
* {@inheritDoc}
*/
protected void initComponent() {
super.initComponent();
}
/**
* {@inheritDoc}
*/
protected void deinitialize() {
super.deinitialize();
}
/**
* {@inheritDoc}
*/
public void pointerPressed(int x, int y) {
if(iosMode) {
super.pointerPressed(x, y);
}
pressX = x;
}
/**
* {@inheritDoc}
*/
public void pointerDragged(int x, int y) {
dragged = true;
deltaX = pressX - x;
if(!iosMode) {
button.setText(on);
int left = Math.max(0, buttonWidth - deltaX);
int right = Math.min(buttonWidth, deltaX);
if(deltaX < 0) {
left = Math.min(buttonWidth, deltaX * -1);
right = Math.max(0, buttonWidth + deltaX);
}
getUnselectedStyle().setPadding(RIGHT, right);
getUnselectedStyle().setPadding(LEFT, left);
getSelectedStyle().setPadding(RIGHT, right);
getSelectedStyle().setPadding(LEFT, left);
if(right < left) {
button.setText(on);
} else {
button.setText(off);
}
revalidate();
}
}
private void animateTo(final boolean value, final int position) {
int switchButtonPadInt = UIManager.getInstance().getThemeConstant("switchButtonPadInt", 16);
if(Display.getInstance().getDisplayWidth() > 480) {
// is retina
switchButtonPadInt *= 2;
}
final Motion current = Motion.createEaseInOutMotion(Math.abs(position), switchMaskImage.getWidth() - 2*switchButtonPadInt, 100);
current.start();
deltaX = position;
getComponentForm().registerAnimated(new Animation() {
public boolean animate() {
deltaX = current.getValue();
if(value) {
deltaX *= -1;
}
dragged = true;
if(current.isFinished()) {
dragged = false;
Form f = getComponentForm();
if(f != null) {
f.deregisterAnimated(this);
}
OnOffSwitch.this.setValue(value);
}
repaint();
return false;
}
public void paint(Graphics g) {
}
});
dragged = true;
}
/**
* {@inheritDoc}
*/
public void pointerReleased(int x, int y) {
if(animationLock) {
return;
}
animationLock = true;
if(iosMode) {
int switchButtonPadInt = UIManager.getInstance().getThemeConstant("switchButtonPadInt", 16);
if(dragged) {
if(deltaX > 0) {
if(deltaX > switchMaskImage.getWidth() / 2 - switchButtonPadInt) {
animateTo(false, deltaX);
} else {
animateTo(true, deltaX);
}
} else {
if(deltaX * -1 > switchMaskImage.getWidth() / 2 - switchButtonPadInt) {
animateTo(true, deltaX);
} else {
animateTo(false, deltaX);
}
}
} else {
animateTo(!value, 0);
}
animationLock = false;
return;
} else {
if(!dragged) {
flip();
} else {
int w = buttonWidth;
deltaX = pressX - x;
int left = Math.max(0, w - deltaX);
int right = Math.min(w, deltaX);
if(deltaX < 0) {
left = Math.min(buttonWidth, deltaX * -1);
right = Math.max(0, buttonWidth + deltaX);
}
if(right < left) {
setValue(true);
} else {
setValue(false);
}
updateButton();
animateLayoutAndWait(150);
}
}
dragged = false;
animationLock = false;
}
/**
* Label for the on mode
* @return the on
*/
public String getOn() {
return on;
}
/**
* Label for the on mode
* @param on the on to set
*/
public void setOn(String on) {
this.on = on;
initialize();
}
/**
* Label for the off mode
* @return the off
*/
public String getOff() {
return off;
}
/**
* Label for the off mode
* @param off the off to set
*/
public void setOff(String off) {
this.off = off;
initialize();
}
/**
* The value of the switch
* @return the value
*/
public boolean isValue() {
return value;
}
/**
* The value of the switch
* @param value the value to set
*/
public void setValue(boolean value) {
boolean orig = animationLock;
animationLock = true;
boolean fireEvent = this.value != value;
if(fireEvent) {
this.value = value;
if(button != null) {
button.setSelected(value);
}
fireActionEvent();
if(iosMode) {
repaint();
} else {
updateButton();
if(isInitialized()){
animateLayoutAndWait(150);
}
}
}
animationLock = orig;
}
/**
* {@inheritDoc}
*/
public String[] getPropertyNames() {
return new String[] {
"value", "on", "off"
};
}
/**
* Some components may optionally generate a state which can then be restored
* using setCompnentState(). This method is used by the UIBuilder.
* @return the component state or null for undefined state.
*/
public Object getComponentState() {
if(value) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
/**
* If getComponentState returned a value the setter can update the value and restore
* the prior state.
* @param state the non-null state
*/
public void setComponentState(Object state) {
value = ((Boolean)state).booleanValue();
}
/**
* {@inheritDoc}
*/
public Class[] getPropertyTypes() {
return new Class[] {
Boolean.class,
String.class,
String.class
};
}
/**
* {@inheritDoc}
*/
public Object getPropertyValue(String name) {
if(name.equals("on")) {
return on;
}
if(name.equals("off")) {
return off;
}
if(name.equals("value")) {
if(value) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
return null;
}
/**
* {@inheritDoc}
*/
public String setPropertyValue(String name, Object value) {
if(name.equals("on")) {
setOn((String)value);
return null;
}
if(name.equals("off")) {
setOff((String)value);
return null;
}
if(name.equals("value")) {
setValue(((Boolean)value).booleanValue());
return null;
}
return super.setPropertyValue(name, value);
}
/**
* iOS 7 changed the switch to not include any text
* @return the noTextMode
*/
public boolean isNoTextMode() {
return noTextMode;
}
/**
* iOS 7 changed the switch to not include any text
* @param noTextMode the noTextMode to set
*/
public void setNoTextMode(boolean noTextMode) {
this.noTextMode = noTextMode;
}
/**
* Gets the image that is used for the "On" state of the switch.
*
* @return The image that will be used for the "On" state. If the "onOffIOSModeBool" theme constant
* is false and the switchOnImage hasn't been explicitly set by the {@link #setSwitchOnImage(com.codename1.ui.Image) }
* method, then this will return null.
*
* @see #setSwitchOnImage(com.codename1.ui.Image)
* @see #getSwitchOffImage()
* @see #getSwitchMaskImage()
*/
public Image getSwitchOnImage() {
if (iosMode) {
return switchOnImage;
}
return null;
}
/**
* Sets the image that should be used for the "Off" state. Note: This option is only used
* if the "onOffIOSModeBool" theme constant is enabled.
*
* Note: If the "onOffIOSModeBool" theme constant is false and you set this image, you must also
* set the "Off" image with {@link #setSwitchOffImage(com.codename1.ui.Image) } and the
* "Mask" image with {@link #setSwitchMaskImage(com.codename1.ui.Image) }.
*
* @param switchOnImage the switchOnImage to set
*
* @see #getSwitchOnImage()
* @see #setSwitchMaskImage(com.codename1.ui.Image)
* @see #setSwitchOffImage(com.codename1.ui.Image)
*/
public void setSwitchOnImage(Image switchOnImage) {
this.switchOnImage = switchOnImage;
iosMode = true;
}
/**
* Gets the image that is used for the "Off" state of the switch.
*
* @return The image that will be used for the "Off" state. If the "onOffIOSModeBool" theme constant
* is false and the switchOffImage hasn't been explicitly set by the {@link #setSwitchOffImage(com.codename1.ui.Image) }
* method, then this will return null.
*
* @see #setSwitchOffImage(com.codename1.ui.Image)
* @see #getSwitchOnImage()
* @see #getSwitchMaskImage()
*/
public Image getSwitchOffImage() {
if (iosMode) {
return switchOffImage;
}
return null;
}
/**
* Sets the image that should be used for the "Off" state. Note: This option is only used
* if the "onOffIOSModeBool" theme constant is enabled.
*
* Note: If the "onOffIOSModeBool" theme constant is false and you set this image, you must also
* set the "On" image with {@link #setSwitchOnImage(com.codename1.ui.Image) } and the
* "Mask" image with {@link #setSwitchMaskImage(com.codename1.ui.Image) }.
*
* @param switchOffImage the switchOffImage to set
*
* @see #getSwitchOffImage()
* @see #setSwitchMaskImage(com.codename1.ui.Image)
* @see #setSwitchOnImage(com.codename1.ui.Image)
*/
public void setSwitchOffImage(Image switchOffImage) {
this.switchOffImage = switchOffImage;
this.iosMode = true;
}
/**
* Gets the image that is used for the mask. Note: This option is only used
* if the "onOffIOSModeBool" theme constant is enabled.
*
* This image will generally
* consist of an opaque border/frame with a transparent center such that the
* "on" and "off" images can be painted inside the transparent part, and anything
* that is outside that center will be painted over by the frame, or clipped.
*
* @return the switchMaskImage image if the "onOffIOSModeBool" flag is set or the mask
* image has been explicitly set on this OnOffSwitch using {@link #setSwitchMaskImage(com.codename1.ui.Image) }
*
* @see #getSwitchOffImage()
* @see #getSwitchOffImage()
* @see #setSwitchOnImage(com.codename1.ui.Image)
* @see #getSwitchMaskImage()
* @see #setSwitchMaskImage(com.codename1.ui.Image)
*/
public Image getSwitchMaskImage() {
if (iosMode) {
return switchMaskImage;
}
return null;
}
/**
* Sets the image that should be used for the "On" state.
*
* Note: If the "onOffIOSModeBool" is false and you set this mask, you must also
* set the "On" image with {@link #setSwitchOnImage(com.codename1.ui.Image) } and the
* "Off" image with {@link #setSwitchOffImage(com.codename1.ui.Image) }.
* @param switchMaskImage the switchMaskImage image to set as the mask for the on-off
* switch.
*/
public void setSwitchMaskImage(Image switchMaskImage) {
this.switchMaskImage = switchMaskImage;
iosMode = true; // If we are explicitly setting this, then we will enable
// iosMode automatically because that is the only
// state in which these images will be used.
}
}