/* * 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.Graphics; 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; /** * <p>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.</p> * * <p> * Simple use cases include just the button as a standalone: * </p> * <script src="https://gist.github.com/codenameone/f6820a6b0c781e5bb5ffa8004c5b5f2e.js"></script> * <p> * The button can also nest sub actions * </p> * <script src="https://gist.github.com/codenameone/aa4180054368f61176c55979010d757b.js"></script> * <img src="http://www.codenameone.com/img/blog/floating-action.png" alt="Floating Button" /> * * @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; } private List<FloatingActionButton> subMenu; private String text; private int shadowOpacity = 100; private Dialog current; private boolean rectangle; private boolean isBadge; /** * 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) { FontImage image = FontImage.createMaterial(icon, "FloatingActionButton", size); sizeMm = size; setIcon(image); setText(""); this.text = text; setUIID("FloatingActionButton"); Style all = getAllStyles(); all.setAlignment(CENTER); updateBorder(); } /** * 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); 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); } /** * 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<FloatingActionButton>(); } subMenu.add(sub); return sub; } @Override protected Dimension calcPreferredSize() { if(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); } @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<FloatingActionButton> fabs) { Container con = new Container(new BoxLayout(BoxLayout.Y_AXIS)); for (FloatingActionButton next : subMenu) { next.setPreferredW(getWidth()); Container c = new Container(new BorderLayout()); Label txt = new Label(next.text); txt.setUIID("FloatingActionText"); c.add(BorderLayout.CENTER, FlowLayout.encloseRight(txt)); c.add(BorderLayout.EAST, next); con.add(c); } 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); } }