com.codename1.components.FloatingActionButton Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2010, Oracle 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. Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.components;
import com.codename1.ui.Button;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.animations.CommonTransitions;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.FlowLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.plaf.RoundBorder;
import com.codename1.ui.plaf.Style;
import java.util.ArrayList;
import java.util.List;
/**
* Floating action buttons are a material design element used to promote a special action in a Form.
* They are represented as a floating circle with a flat icon floating above the UI typically in the bottom right
* area.
*
*
* Simple use cases include just the button as a standalone:
*
*
*
* The button can also nest sub actions
*
*
*
*
* @author Chen
*/
public class FloatingActionButton extends Button {
/**
* The default icon size for the fab icon in millimeters
* @return the fabDefaultSize
*/
public static float getIconDefaultSize() {
return fabDefaultSize;
}
/**
* The default icon size for the fab icon in millimeters
* @param aFabDefaultSize the fabDefaultSize to set
*/
public static void setIconDefaultSize(float aFabDefaultSize) {
fabDefaultSize = aFabDefaultSize;
}
/**
* The FloatingActionButton tries to size/pad itself automatically but
* this means that manual padding is ignored. Setting this to false
* disables that behavior
* @return the autoSizing
*/
public static boolean isAutoSizing() {
return autoSizing;
}
/**
* The FloatingActionButton tries to size/pad itself automatically but
* this means that manual padding is ignored. Setting this to false
* disables that behavior
* @param aAutoSizing the autoSizing to set
*/
public static void setAutoSizing(boolean aAutoSizing) {
autoSizing = aAutoSizing;
}
private List subMenu;
/**
* The UIID of the sub action texts can be overriden. It defaults to {@code FloatingActionText}
*/
private String floatingActionTextUIID = "FloatingActionText";
private String text;
private int shadowOpacity = 100;
private Dialog current;
private boolean rectangle;
private boolean isBadge;
/**
* The FloatingActionButton tries to size/pad itself automatically but
* this means that manual padding is ignored. Setting this to false
* disables that behavior
*/
private static boolean autoSizing = true;
/**
* The default icon size for the fab
*/
private static float fabDefaultSize = 3.8f;
private float sizeMm = fabDefaultSize;
/**
* Constructor
*
* @param icon one of the FontImage.MATERIAL_* constants
* @param text the text of the sub FloatingActionButton
* @param size the size in millimeters
*/
protected FloatingActionButton(char icon, String text, float size) {
this(icon, text, "FloatingActionButton", size);
}
/**
* Constructor
*
* @param icon one of the FontImage.MATERIAL_* constants
* @param text the text of the sub FloatingActionButton
* @param uiid the uiid of the FAB
* @param size the size in millimeters
*/
protected FloatingActionButton(char icon, String text, String uiid, float size) {
FontImage image = FontImage.createMaterial(icon, uiid, size);
setGap(0);
image.setBgTransparency(0);
sizeMm = size;
setIcon(image);
setText("");
this.text = text;
setUIID(uiid);
Style all = getAllStyles();
all.setAlignment(CENTER);
updateBorder();
}
/**
* Overriden to update the icon
* {@inheritDoc}
*/
@Override
public void setUIID(String id) {
super.setUIID(id);
FontImage i = (FontImage)getIcon();
if(i != null) {
Style all = getAllStyles();
all.setAlignment(CENTER);
updateBorder();
FontImage image = FontImage.createMaterial(i.getText().charAt(0), id, sizeMm);
image.setBgTransparency(0);
setIcon(image);
}
}
/**
* This constructor is used by text badges
*/
private FloatingActionButton(String text) {
super.setText(text);
rectangle = true;
shadowOpacity = 0;
setUIID("Badge");
updateBorder();
isBadge = true;
}
private void updateBorder() {
getUnselectedStyle().setBorder(RoundBorder.create().
color(getUnselectedStyle().getBgColor()).
shadowOpacity(shadowOpacity).rectangle(rectangle));
getSelectedStyle().setBorder(RoundBorder.create().
color(getSelectedStyle().getBgColor()).
shadowOpacity(shadowOpacity).rectangle(rectangle));
getPressedStyle().setBorder(RoundBorder.create().
color(getPressedStyle().getBgColor()).
shadowOpacity(shadowOpacity).rectangle(rectangle));
}
/**
* We override this method to track style changes to the background color and map them to the border
*
* {@inheritDoc}
*/
@Override
public void styleChanged(String propertyName, Style source) {
if(propertyName.equals(Style.BG_COLOR)) {
updateBorder();
}
if(getIcon() instanceof FontImage && propertyName.equals(Style.FG_COLOR)) {
FontImage i = (FontImage)getIcon();
FontImage image = FontImage.createMaterial(i.getText().charAt(0), "FloatingActionButton", sizeMm);
image.setBgTransparency(0);
setIcon(image);
}
}
/**
* Creates a text badge
* @param text the text of the badge
* @return a badge component
*/
public static FloatingActionButton createBadge(String text) {
return new FloatingActionButton(text);
}
/**
* a factory method to create a FloatingActionButton.
*
* @param icon one of the FontImage.MATERIAL_* constants
* @return a FloatingActionButton instance
*/
public static FloatingActionButton createFAB(char icon) {
return new FloatingActionButton(icon, null, fabDefaultSize);
}
/**
* a factory method to create a FloatingActionButton.
*
* @param icon one of the FontImage.MATERIAL_* constants
* @param uiid the uiid for the fab
* @return a FloatingActionButton instance
*/
public static FloatingActionButton createFAB(char icon, String uiid) {
return new FloatingActionButton(icon, null, uiid, fabDefaultSize);
}
/**
* Adds a sub FAB to the FloatingActionButton instance. Once pressed all its
* sub FAB's are displayed.
*
* @param icon one of the FontImage.MATERIAL_* constants
* @param text the text of the sub FloatingActionButton
*
* @return a FloatingActionButton instance for the sub FAB added
*/
public FloatingActionButton createSubFAB(char icon, String text) {
FloatingActionButton sub = new FloatingActionButton(icon, text, 2.8f);
if (subMenu == null) {
subMenu = new ArrayList();
}
subMenu.add(sub);
return sub;
}
@Override
protected Dimension calcPreferredSize() {
if(autoSizing && getIcon() != null) {
return new Dimension(getIcon().getWidth() * 11 / 4, getIcon().getHeight() * 11 / 4);
}
return super.calcPreferredSize();
}
/**
* This is a utility method to bind the FAB to a given Container, it will return a new container to add or will
* use the layered pane if the container is a content pane.
*
* @param cnt the Container to add the FAB to
* @return a new Container that contains the cnt and the FAB on top or null in the case of a content pane
*/
public Container bindFabToContainer(Component cnt) {
return bindFabToContainer(cnt, Component.RIGHT, Component.BOTTOM);
}
/**
* This is a utility method to bind the FAB to a given Container, it will return a new container to add or will
* use the layered pane if the container is a content pane.
*
* @param cnt the Container to add the FAB to
* @param orientation one of Component.RIGHT/LEFT/CENTER
* @param valign one of Component.TOP/BOTTOM/CENTER
*
* @return a new Container that contains the cnt and the FAB on top or null in the case of a content pane
*/
public Container bindFabToContainer(Component cnt, int orientation, int valign) {
FlowLayout flow = new FlowLayout(orientation);
flow.setValign(valign);
Form f = cnt.getComponentForm();
if(f != null && (f.getContentPane() == cnt || f == cnt)) {
// special case for content pane installs the button directly on the content pane
Container layers = f.getLayeredPane(getClass(), true);
layers.setLayout(flow);
layers.add(this);
return null;
}
Container conUpper = new Container(flow);
conUpper.add(this);
return LayeredLayout.encloseIn(cnt, conUpper);
}
/**
* Removes the floating action button from its parent
*/
public void unbind() {
Container cnt = getParent();
remove();
if(cnt != null) {
cnt.remove();
}
}
@Override
public void setText(String text) {
if(isBadge) {
super.setText(text);
}
this.text = text;
}
@Override
protected void fireActionEvent(int x, int y) {
Form current = Display.getInstance().getCurrent();
if(current instanceof Dialog) {
((Dialog)current).dispose();
}
super.fireActionEvent(x, y);
}
@Override
public void released(int x, int y) {
super.released(x, y);
if (current != null) {
current.dispose();
current = null;
}
//if this fab has sub fab's display them
if (subMenu != null) {
final Container con = createPopupContent(subMenu);
Dialog d = new Dialog();
d.setDialogUIID("Container");
d.getContentPane().setUIID("Container");
d.setLayout(new BorderLayout());
d.add(BorderLayout.CENTER, con);
for (FloatingActionButton next : subMenu) {
next.current = d;
}
d.setTransitionInAnimator(CommonTransitions.createEmpty());
d.setTransitionOutAnimator(CommonTransitions.createEmpty());
for(Component c : con) {
c.setVisible(false);
}
Form f = getComponentForm();
int oldTint = f.getTintColor();
f.setTintColor(0);
d.setBlurBackgroundRadius(-1);
d.addShowListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
for(Component c : con) {
c.setY(con.getHeight());
c.setVisible(true);
}
con.animateLayout(200);
}
});
showPopupDialog(d);
f.setTintColor(oldTint);
for (FloatingActionButton next : subMenu) {
next.remove();
}
con.removeAll();
}
}
/**
* Creates the popup content container to display on the dialog.
*
* @param fabs List of sub FloatingActionButton
* @return a Container that contains all fabs
*/
protected Container createPopupContent(List fabs) {
Container con = new Container(new BoxLayout(BoxLayout.Y_AXIS));
for (final FloatingActionButton next : subMenu) {
next.setPreferredW(getWidth());
Container c = new Container(new BorderLayout());
Button txt = new Button(next.text);
txt.setUIID(floatingActionTextUIID);
c.add(BorderLayout.CENTER, FlowLayout.encloseRight(txt));
c.add(BorderLayout.EAST, next);
con.add(c);
txt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
next.pressed();
next.released();
}
});
}
return con;
}
/**
* Shows the popup Dialog with the sub FABs.
*
* @param dialog the Dialog with all sub FAB's Components
*/
protected void showPopupDialog(Dialog dialog) {
dialog.setPopupDirectionBiasPortrait(Boolean.TRUE);
dialog.showPopupDialog(this);
}
/**
* @return the floatingActionTextUIID
*/
public String getFloatingActionTextUIID() {
return floatingActionTextUIID;
}
/**
* @param floatingActionTextUIID the floatingActionTextUIID to set
*/
public void setFloatingActionTextUIID(String floatingActionTextUIID) {
this.floatingActionTextUIID = floatingActionTextUIID;
}
}